Realtime TradingView Chart
System Instructions
<head>
<title>TradingView Advanced Charts with Replay API</title>
</head>
<body style="margin:0px;">
<!-- A container for the library widget -->
<div id="tv_chart_container"></div>
<!-- The script that loads Advanced Charts -->
<script type="text/javascript" src="charting_library_cloned_data/charting_library/charting_library.js"></script>
<!-- Custom datafeed module -->
<script type="module">
// Simulated data for streaming (keeping your original future-dated data)
const simulatedData = [
{ time: 1725594300000, open: 25093.7, high: 25120.4, low: 25088.4, close: 25113.95, volume: 0 },
{ time: 1725594305000, open: 25120.65, high: 25131.65, low: 25118.15, close: 25126.1, volume: 0 },
{ time: 1725594310000, open: 25121.85, high: 25121.85, low: 25114.2, close: 25117.85, volume: 0 },
{ time: 1725594315000, open: 25115.6, high: 25118.2, low: 25111.25, close: 25114.4, volume: 0 },
{ time: 1725594320000, open: 25117.6, high: 25117.6, low: 25108.75, close: 25108.75, volume: 0 },
{ time: 1725594325000, open: 25108.5, high: 25122.85, low: 25108.25, close: 25122.85, volume: 0 },
{ time: 1725594330000, open: 25121.4, high: 25125.3, low: 25118.0, close: 25118.0, volume: 0 },
{ time: 1725594335000, open: 25118.2, high: 25118.65, low: 25116.7, close: 25118.35, volume: 0 }
];
// Timeframe arrays to store aggregated data
const dataArrays = {
'5S': [],
'1': [],
'3': [],
'5': [],
'15': [],
'30': [],
'60': [],
'120': [],
'240': [],
'1D': [],
'1W': [],
'1M': []
};
function streamData(data, interval = 5000) {
let index = 0;
return {
next() {
return new Promise((resolve) => {
if (index < data.length) {
setTimeout(() => {
resolve({ value: data[index++], done: false });
}, interval);
} else {
resolve({ done: true });
}
});
},
[Symbol.asyncIterator]() {
return this;
}
};
}
function aggregateData(data, timeframe) {
const aggregated = [];
let currentBucket = null;
const timeframeMs = getTimeframeMs(timeframe);
for (let row of data) {
const bucketTime = Math.floor(row.time / timeframeMs) * timeframeMs;
if (!currentBucket || currentBucket.time !== bucketTime) {
if (currentBucket) {
aggregated.push(currentBucket);
}
currentBucket = {
time: bucketTime,
open: row.open,
high: row.high,
low: row.low,
close: row.close,
volume: row.volume
};
} else {
currentBucket.high = Math.max(currentBucket.high, row.high);
currentBucket.low = Math.min(currentBucket.low, row.low);
currentBucket.close = row.close;
currentBucket.volume += row.volume;
}
}
if (currentBucket) {
aggregated.push(currentBucket);
}
return aggregated;
}
function getTimeframeMs(timeframe) {
const unit = timeframe.slice(-1);
const value = parseInt(timeframe);
switch (unit) {
case 'S': return value * 1000;
case 'D': return value * 24 * 60 * 60 * 1000;
case 'W': return value * 7 * 24 * 60 * 60 * 1000;
case 'M': return value * 30 * 24 * 60 * 60 * 1000;
default: return value * 60 * 1000; // Assume minutes if no unit specified
}
}
// Start streaming data and populating timeframe arrays
async function startStreaming() {
const dataStream = streamData(simulatedData, 5000); // 5 second interval for demonstration
for await (const newData of dataStream) {
console.log("New data received:", new Date(newData.time), newData);
// Update all timeframe arrays
for (let timeframe in dataArrays) {
dataArrays[timeframe] = aggregateData([...dataArrays[timeframe], newData], timeframe);
}
// Log the updated arrays
console.log("Updated dataArrays:", JSON.parse(JSON.stringify(dataArrays)));
// Notify subscribers about new data
notifySubscribers(newData);
}
console.log("Streaming completed");
}
// Start streaming immediately
startStreaming();
// Subscription handling
const subscribers = new Map();
function notifySubscribers(newData) {
for (let [subscriberUID, handler] of subscribers) {
const { symbolInfo, resolution, callback } = handler;
const aggregatedData = aggregateData([newData], resolution)[0];
callback(aggregatedData);
}
}
// DatafeedConfiguration implementation
const configurationData = {
supported_resolutions: ["5S", "1", "3", "5", "15", "30", "60", "120", "240", "1D", "1W", "1M"],
exchanges: [{
value: 'Kraken',
name: 'Kraken',
desc: 'Kraken bitcoin exchange',
}],
symbols_types: [{
name: 'crypto',
value: 'crypto',
}],
};
// Modified Datafeed object
const Datafeed = {
onReady: (callback) => {
console.log('[onReady]: Method call');
setTimeout(() => callback(configurationData));
},
searchSymbols: (userInput, exchange, symbolType, onResultReadyCallback) => {
console.log('[searchSymbols]: Method call');
// Implement symbol search logic here
onResultReadyCallback([]);
},
resolveSymbol: (symbolName, onSymbolResolvedCallback, onResolveErrorCallback) => {
console.log('[resolveSymbol]: Method call', symbolName);
// Implement symbol resolution logic here
const symbolInfo = {
name: symbolName,
description: symbolName,
type: 'crypto',
session: '24x7',
timezone: 'Etc/UTC',
minmov: 1,
pricescale: 100,
has_intraday: true,
has_seconds: true,
intraday_multipliers: ['1', '60'],
seconds_multipliers: ["5"],
supported_resolutions: configurationData.supported_resolutions,
volume_precision: 2,
data_status: 'streaming',
};
onSymbolResolvedCallback(symbolInfo);
},
getBars: (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => {
const { from, to, firstDataRequest } = periodParams;
console.log('[getBars]: Method call', symbolInfo, resolution, new Date(from * 1000), new Date(to * 1000));
// Log the timeframe array for the selected resolution
console.log(`[getBars]: Timeframe array for ${resolution}:`, dataArrays[resolution]);
// Get data from the appropriate timeframe array
const bars = dataArrays[resolution].filter(bar => {
const barTime = bar.time / 1000; // Convert milliseconds to seconds
return barTime >= from && barTime < to;
});
console.log(`[getBars]: returned ${bars.length} bar(s)`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
},
subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscriberUID, onResetCacheNeededCallback) => {
console.log('[subscribeBars]: Method call with subscriberUID:', subscriberUID);
subscribers.set(subscriberUID, { symbolInfo, resolution, callback: onRealtimeCallback });
},
unsubscribeBars: (subscriberUID) => {
console.log('[unsubscribeBars]: Method call with subscriberUID:', subscriberUID);
subscribers.delete(subscriberUID);
},
};
// TradingView widget initialization
window.tvWidget = new TradingView.widget({
symbol: 'Kraken:BTC/USD',
interval: '5S',
fullscreen: true,
container: 'tv_chart_container',
datafeed: Datafeed,
enabled_features: ["dont_show_boolean_study_arguments", "seconds_resolution"],
library_path: '../charting_library_cloned_data/charting_library/',
time_frames: [
{ text: "1m", resolution: "1", description: "1 Minute", title: "1m" },
],
overrides: {
"mainSeriesProperties.candleStyle.upColor": "#26a69a",
"mainSeriesProperties.candleStyle.downColor": "#ef5350",
"mainSeriesProperties.candleStyle.drawWick": true,
"mainSeriesProperties.candleStyle.drawBorder": true,
"mainSeriesProperties.candleStyle.borderColor": "#378658",
"mainSeriesProperties.candleStyle.borderUpColor": "#26a69a",
"mainSeriesProperties.candleStyle.borderDownColor": "#ef5350",
"mainSeriesProperties.candleStyle.wickUpColor": "#26a69a",
"mainSeriesProperties.candleStyle.wickDownColor": "#ef5350",
},
time: simulatedData[0].time / 1000, // Set initial time to the first data point
});
</script>
</body>
<!DOCTYPE HTML>
<html>
<head>
<title>TradingView Advanced Charts with Replay API</title>
</head>
<body style="margin:0px;">
<!-- A container for the library widget -->
<div id="tv_chart_container"></div>
<!-- The script that loads Advanced Charts -->
<script type="text/javascript" src="charting_library_cloned_data/charting_library/charting_library.js"></script>
<!-- Custom datafeed module -->
<script type="module">
// Simulated data for streaming
const simulatedData = [
{ time: 1725594300000, open: 25093.7, high: 25120.4, low: 25088.4, close: 25113.95, volume: 0 },
{ time: 1725594305000, open: 25120.65, high: 25131.65, low: 25118.15, close: 25126.1, volume: 0 },
{ time: 1725594310000, open: 25121.85, high: 25121.85, low: 25114.2, close: 25117.85, volume: 0 },
{ time: 1725594315000, open: 25115.6, high: 25118.2, low: 25111.25, close: 25114.4, volume: 0 },
{ time: 1725594320000, open: 25117.6, high: 25117.6, low: 25108.75, close: 25108.75, volume: 0 },
{ time: 1725594325000, open: 25108.5, high: 25122.85, low: 25108.25, close: 25122.85, volume: 0 },
{ time: 1725594330000, open: 25121.4, high: 25125.3, low: 25118.0, close: 25118.0, volume: 0 },
{ time: 1725594335000, open: 25118.2, high: 25118.65, low: 25116.7, close: 25118.35, volume: 0 }
];
function streamData(data, interval = 5000) {
let index = 0;
return {
next() {
return new Promise((resolve) => {
if (index < data.length) {
setTimeout(() => {
resolve({ value: data[index++], done: false });
}, interval);
} else {
resolve({ done: true });
}
});
},
[Symbol.asyncIterator]() {
return this;
}
};
}
// Start streaming data
async function startStreaming() {
const dataStream = streamData(simulatedData, 5000);
for await (const newData of dataStream) {
console.log("New data received:", new Date(newData.time), newData);
notifySubscribers(newData);
}
console.log("Streaming completed");
}
// Start streaming immediately
startStreaming();
// Subscription handling
const subscribers = new Map();
function notifySubscribers(newData) {
for (let [subscriberUID, handler] of subscribers) {
handler.callback(newData);
}
}
// DatafeedConfiguration implementation
const configurationData = {
supported_resolutions: ["5S", "1", "3", "5", "15", "30", "60", "120", "240", "1D", "1W", "1M"],
exchanges: [{
value: 'Kraken',
name: 'Kraken',
desc: 'Kraken bitcoin exchange',
}],
symbols_types: [{
name: 'crypto',
value: 'crypto',
}],
};
// Modified Datafeed object
const Datafeed = {
onReady: (callback) => {
console.log('[onReady]: Method call');
setTimeout(() => callback(configurationData));
},
searchSymbols: (userInput, exchange, symbolType, onResultReadyCallback) => {
console.log('[searchSymbols]: Method call');
onResultReadyCallback([]);
},
resolveSymbol: (symbolName, onSymbolResolvedCallback, onResolveErrorCallback) => {
console.log('[resolveSymbol]: Method call', symbolName);
const symbolInfo = {
name: symbolName,
description: symbolName,
type: 'crypto',
session: '24x7',
timezone: 'Etc/UTC',
minmov: 1,
pricescale: 100,
has_intraday: true,
has_seconds: true,
intraday_multipliers: ['1', '60'],
seconds_multipliers: ["5"],
supported_resolutions: configurationData.supported_resolutions,
volume_precision: 2,
data_status: 'streaming',
};
onSymbolResolvedCallback(symbolInfo);
},
getBars: (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => {
const { from, to, firstDataRequest } = periodParams;
console.log('[getBars]: Method call', symbolInfo, resolution, new Date(from * 1000), new Date(to * 1000));
// Return an empty array for historical data as we are only focused on realtime updates
onHistoryCallback([], { noData: true });
},
subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscriberUID, onResetCacheNeededCallback) => {
console.log('[subscribeBars]: Method call with subscriberUID:', subscriberUID);
subscribers.set(subscriberUID, { symbolInfo, resolution, callback: onRealtimeCallback });
},
unsubscribeBars: (subscriberUID) => {
console.log('[unsubscribeBars]: Method call with subscriberUID:', subscriberUID);
subscribers.delete(subscriberUID);
},
};
// TradingView widget initialization
window.tvWidget = new TradingView.widget({
symbol: 'Kraken:BTC/USD',
interval: '5S',
fullscreen: true,
container: 'tv_chart_container',
datafeed: Datafeed,
enabled_features: ["dont_show_boolean_study_arguments", "seconds_resolution"],
library_path: '../charting_library_cloned_data/charting_library/',
time_frames: [
{ text: "1m", resolution: "1", description: "1 Minute", title: "1m" },
],
overrides: {
"mainSeriesProperties.candleStyle.upColor": "#26a69a",
"mainSeriesProperties.candleStyle.downColor": "#ef5350",
"mainSeriesProperties.candleStyle.drawWick": true,
"mainSeriesProperties.candleStyle.drawBorder": true,
"mainSeriesProperties.candleStyle.borderColor": "#378658",
"mainSeriesProperties.candleStyle.borderUpColor": "#26a69a",
"mainSeriesProperties.candleStyle.borderDownColor": "#ef5350",
"mainSeriesProperties.candleStyle.wickUpColor": "#26a69a",
"mainSeriesProperties.candleStyle.wickDownColor": "#ef5350",
},
time: simulatedData[0].time / 1000,
});
</script>
</body>
</html>
Removed Timeframe Arrays and Aggregation Logic: We removed the dataArrays object and the aggregateData and getTimeframeMs functions because we're only dealing with realtime updates and don't need to store or aggregate data.
Simplified streamData: The streamData function remains the same, simulating a stream of data.
Simplified startStreaming: The startStreaming function now simply iterates through the simulated data stream and calls notifySubscribers with each new data point.
Modified notifySubscribers: The notifySubscribers function now directly passes the newData to the onRealtimeCallback without any aggregation.
Modified getBars: The getBars function now always returns an empty array with noData: true. This indicates to TradingView that there is no historical data available, and it should rely solely on the realtime updates.
The streamData function simulates a stream of new bar data. startStreaming iterates through this stream and sends each new bar to notifySubscribers. notifySubscribers directly calls the onRealtimeCallback provided by TradingView for each subscriber (chart instance). TradingView's replay method internally handles updating the chart based on these realtime updates. Since getBars returns no historical data, the chart initially starts empty and is populated solely by the realtime updates.
time: simulatedData[0].time / 1000,
time: simulatedData[0].time / 1000, // Corrected: Divide by 1000
<head>
<title>TradingView Advanced Charts with Replay API</title>
</head>
<body style="margin:0px;">
<!-- A container for the library widget -->
<div id="tv_chart_container"></div>
<!-- The script that loads Advanced Charts -->
<script type="text/javascript" src="charting_library_cloned_data/charting_library/charting_library.js"></script>
<!-- Custom datafeed module -->
<script type="module">
// Simulated data for streaming
const simulatedData = [
{ time: 1725594300000, open: 25093.7, high: 25120.4, low: 25088.4, close: 25113.95, volume: 0 },
{ time: 1725594305000, open: 25120.65, high: 25131.65, low: 25118.15, close: 25126.1, volume: 0 },
{ time: 1725594310000, open: 25121.85, high: 25121.85, low: 25114.2, close: 25117.85, volume: 0 },
{ time: 1725594315000, open: 25115.6, high: 25118.2, low: 25111.25, close: 25114.4, volume: 0 },
{ time: 1725594320000, open: 25117.6, high: 25117.6, low: 25108.75, close: 25108.75, volume: 0 },
{ time: 1725594325000, open: 25108.5, high: 25122.85, low: 25108.25, close: 25122.85, volume: 0 },
{ time: 1725594330000, open: 25121.4, high: 25125.3, low: 25118.0, close: 25118.0, volume: 0 },
{ time: 1725594335000, open: 25118.2, high: 25118.65, low: 25116.7, close: 25118.35, volume: 0 }
];
function streamData(data, interval = 5000) {
let index = 0;
return {
next() {
return new Promise((resolve) => {
if (index < data.length) {
setTimeout(() => {
resolve({ value: data[index++], done: false });
}, interval);
} else {
resolve({ done: true });
}
});
},
[Symbol.asyncIterator]() {
return this;
}
};
}
// Start streaming data
async function startStreaming() {
const dataStream = streamData(simulatedData, 5000);
for await (const newData of dataStream) {
console.log("New data received:", new Date(newData.time), newData);
notifySubscribers(newData);
}
console.log("Streaming completed");
}
// Start streaming immediately
startStreaming();
// Subscription handling
const subscribers = new Map();
function notifySubscribers(newData) {
for (let [subscriberUID, handler] of subscribers) {
handler.callback(newData);
}
}
// DatafeedConfiguration implementation
const configurationData = {
supported_resolutions: ["5S", "1", "3", "5", "15", "30", "60", "120", "240", "1D", "1W", "1M"],
exchanges: [{
value: 'Kraken',
name: 'Kraken',
desc: 'Kraken bitcoin exchange',
}],
symbols_types: [{
name: 'crypto',
value: 'crypto',
}],
};
// Modified Datafeed object
const Datafeed = {
onReady: (callback) => {
console.log('[onReady]: Method call');
setTimeout(() => callback(configurationData));
},
searchSymbols: (userInput, exchange, symbolType, onResultReadyCallback) => {
console.log('[searchSymbols]: Method call');
onResultReadyCallback([]);
},
resolveSymbol: (symbolName, onSymbolResolvedCallback, onResolveErrorCallback) => {
console.log('[resolveSymbol]: Method call', symbolName);
const symbolInfo = {
name: symbolName,
description: symbolName,
type: 'crypto',
session: '24x7',
timezone: 'Etc/UTC',
minmov: 1,
pricescale: 100,
has_intraday: true,
has_seconds: true,
intraday_multipliers: ['1', '60'],
seconds_multipliers: ["5"],
supported_resolutions: configurationData.supported_resolutions,
volume_precision: 2,
data_status: 'streaming',
};
onSymbolResolvedCallback(symbolInfo);
},
getBars: (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => {
const { from, to, firstDataRequest } = periodParams;
console.log('[getBars]: Method call', symbolInfo, resolution, new Date(from * 1000), new Date(to * 1000));
// Return an empty array for historical data as we are only focused on realtime updates
onHistoryCallback([], { noData: true });
},
subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscriberUID, onResetCacheNeededCallback) => {
console.log('[subscribeBars]: Method call with subscriberUID:', subscriberUID);
subscribers.set(subscriberUID, { symbolInfo, resolution, callback: onRealtimeCallback });
},
unsubscribeBars: (subscriberUID) => {
console.log('[unsubscribeBars]: Method call with subscriberUID:', subscriberUID);
subscribers.delete(subscriberUID);
},
};
// TradingView widget initialization
window.tvWidget = new TradingView.widget({
symbol: 'Kraken:BTC/USD',
interval: '5S',
fullscreen: true,
container: 'tv_chart_container',
datafeed: Datafeed,
enabled_features: ["dont_show_boolean_study_arguments", "seconds_resolution"],
library_path: '../charting_library_cloned_data/charting_library/',
time_frames: [
{ text: "1m", resolution: "1", description: "1 Minute", title: "1m" },
],
overrides: {
"mainSeriesProperties.candleStyle.upColor": "#26a69a",
"mainSeriesProperties.candleStyle.downColor": "#ef5350",
"mainSeriesProperties.candleStyle.drawWick": true,
"mainSeriesProperties.candleStyle.drawBorder": true,
"mainSeriesProperties.candleStyle.borderColor": "#378658",
"mainSeriesProperties.candleStyle.borderUpColor": "#26a69a",
"mainSeriesProperties.candleStyle.borderDownColor": "#ef5350",
"mainSeriesProperties.candleStyle.wickUpColor": "#26a69a",
"mainSeriesProperties.candleStyle.wickDownColor": "#ef5350",
},
time: simulatedData[0].time * 1000,
});
</script>
</body>
Remove the time option entirely: TradingView should automatically adjust the initial time based on the first data point it receives.
window.tvWidget = new TradingView.widget({ // ... (rest of your configuration) // Remove the 'time' option });Double-check the timezone: Make sure the timezone specified in your resolveSymbol function ('Etc/UTC' in your code) matches the timezone of your simulatedData. Any discrepancies can lead to incorrect date display.
Inspect the network requests: Use your browser's developer tools (Network tab) to monitor the requests made by TradingView. Look for any errors or unexpected responses related to time or data retrieval.
Test with a simpler dataset: Create a very basic simulatedData array with only a few data points to isolate whether the issue is with the data itself or with the way TradingView is processing it.
Consult TradingView's documentation: There might be specific guidelines or limitations regarding the initial time setting that are not readily apparent.
subscribeBars is called: When you initialize the TradingView widget and set the data_status to streaming in the resolveSymbol function, TradingView calls your subscribeBars method.onRealtimeCallback is set: In subscribeBars, you provide a callback function (onRealtimeCallback) that TradingView will use to send you new data updates.New data arrives: Your notifySubscribers function, which is triggered by your simulated data stream, sends new data to TradingView through the onRealtimeCallback.TradingView processes the data: TradingView internally receives the new data and determines how to update the chart based on the resolution and timeframe.replay is called (internally): TradingView's internal logic calls the replay function, which is responsible for updating the chart's data and redrawing it to reflect the new information. This is done without you directly calling the replay function.
You: The data provider, sending updates like a news feed.TradingView Charting Library: The newsreader, receiving updates and deciding how to present them on the screen.replay function: The internal mechanism the newsreader uses to update the display based on new information.
<!DOCTYPE HTML>
<html>
<head>
<title>TradingView Advanced Charts with Replay API</title>
</head>
<body style="margin:0px;">
<div id="tv_chart_container"></div>
<script type="text/javascript" src="charting_library_cloned_data/charting_library/charting_library.js"></script>
<script type="module">
const simulatedData = [
{ time: 1725594300000, open: 25093.7, high: 25120.4, low: 25088.4, close: 25113.95, volume: 0 },
// ... (rest of your simulated data)
];
const dataArrays = {
'5S': [],
'1': [],
'3': [],
// ... (other timeframes)
};
function streamData(data, interval = 5000) {
// ... (same as before)
}
async function startStreaming() {
const dataStream = streamData(simulatedData, 5000);
for await (const newData of dataStream) {
console.log("New data received:", new Date(newData.time), newData);
// Update only the 5S array for now
dataArrays['5S'].push(newData);
notifySubscribers(newData);
}
console.log("Streaming completed");
}
startStreaming();
const subscribers = new Map();
function notifySubscribers(newData) {
// ... (same as before)
}
const configurationData = {
// ... (same as before)
};
const Datafeed = {
onReady: (callback) => {
// ... (same as before)
},
searchSymbols: (userInput, exchange, symbolType, onResultReadyCallback) => {
// ... (same as before)
},
resolveSymbol: (symbolName, onSymbolResolvedCallback, onResolveErrorCallback) => {
// ... (same as before)
},
getBars: (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => {
const { from, to, firstDataRequest } = periodParams;
console.log('[getBars]: Method call', symbolInfo, resolution, new Date(from * 1000), new Date(to * 1000));
// Return data from the appropriate timeframe array
const bars = dataArrays[resolution].filter(bar => {
const barTime = bar.time / 1000;
return barTime >= from && barTime < to;
});
console.log(`[getBars]: returned ${bars.length} bar(s)`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
},
subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscriberUID, onResetCacheNeededCallback) => {
// ... (same as before)
},
unsubscribeBars: (subscriberUID) => {
// ... (same as before)
},
};
window.tvWidget = new TradingView.widget({
// ... (rest of your widget configuration)
});
</script>
</body>
</html>
dataArrays Object: We reintroduce the dataArrays object to store data for each timeframe separately. For now, we only populate the '5S' array.startStreaming Modification: In startStreaming, we now push each new data point into the dataArrays['5S'] array.getBars Modification: Instead of returning an empty array, we now filter the appropriate timeframe array (dataArrays[resolution]) based on the requested from and to timestamps. This ensures that when you switch timeframes and then back to 5S, you get all the data that has been accumulated in the dataArrays['5S'] array up to that point.
The replay stream (via onRealtimeCallback) continues to populate the dataArrays['5S'] array. When you switch to a different timeframe, TradingView calls getBars with the new resolution and time range. getBars returns the relevant data from the corresponding timeframe array (which might be empty for other timeframes initially). When you switch back to 5S, getBars will return all the data that has been accumulated in dataArrays['5S'], giving you a continuous view of the 5S data.
You'll need to implement logic to populate the other timeframe arrays ('1', '3', etc.) as well, possibly using aggregation or other methods, depending on how you want to handle different resolutions. Consider how you'll handle historical data for timeframes other than 5S – you might need to fetch it initially or provide a way to load it on demand.
<!DOCTYPE HTML>
<html>
<head>
<title>TradingView Advanced Charts with Replay API</title>
</head>
<body style="margin:0px;">
<div id="tv_chart_container"></div>
<script type="text/javascript" src="charting_library_cloned_data/charting_library/charting_library.js"></script>
<script type="module">
const simulatedData = [
{ time: 1725594300000, open: 25093.7, high: 25120.4, low: 25088.4, close: 25113.95, volume: 0 },
{ time: 1725594305000, open: 25120.65, high: 25131.65, low: 25118.15, close: 25126.1, volume: 0 },
{ time: 1725594310000, open: 25121.85, high: 25121.85, low: 25114.2, close: 25117.85, volume: 0 },
{ time: 1725594315000, open: 25115.6, high: 25118.2, low: 25111.25, close: 25114.4, volume: 0 },
{ time: 1725594320000, open: 25117.6, high: 25117.6, low: 25108.75, close: 25108.75, volume: 0 },
{ time: 1725594325000, open: 25108.5, high: 25122.85, low: 25108.25, close: 25122.85, volume: 0 },
{ time: 1725594330000, open: 25121.4, high: 25125.3, low: 25118.0, close: 25118.0, volume: 0 },
{ time: 1725594335000, open: 25118.2, high: 25118.65, low: 25116.7, close: 25118.35, volume: 0 }
];
const dataArrays = {
'5S': [],
'1': [],
'3': [],
'5': [],
'15': [],
'30': [],
'60': [],
'120': [],
'240': [],
'1D': [],
'1W': [],
'1M': []
};
function streamData(data, interval = 5000) {
let index = 0;
return {
next() {
return new Promise((resolve) => {
if (index < data.length) {
setTimeout(() => {
resolve({ value: data[index++], done: false });
}, interval);
} else {
resolve({ done: true });
}
});
},
[Symbol.asyncIterator]() {
return this;
}
};
}
function aggregateData(data, timeframe) {
const aggregated = [];
let currentBucket = null;
const timeframeMs = getTimeframeMs(timeframe);
for (let row of data) {
const bucketTime = Math.floor(row.time / timeframeMs) * timeframeMs;
if (!currentBucket || currentBucket.time !== bucketTime) {
if (currentBucket) {
aggregated.push(currentBucket);
}
currentBucket = {
time: bucketTime,
open: row.open,
high: row.high,
low: row.low,
close: row.close,
volume: row.volume
};
} else {
currentBucket.high = Math.max(currentBucket.high, row.high);
currentBucket.low = Math.min(currentBucket.low, row.low);
currentBucket.close = row.close;
currentBucket.volume += row.volume;
}
}
if (currentBucket) {
aggregated.push(currentBucket);
}
return aggregated;
}
function getTimeframeMs(timeframe) {
const unit = timeframe.slice(-1);
const value = parseInt(timeframe);
switch (unit) {
case 'S': return value * 1000;
case 'D': return value * 24 * 60 * 60 * 1000;
case 'W': return value * 7 * 24 * 60 * 60 * 1000;
case 'M': return value * 30 * 24 * 60 * 60 * 1000;
default: return value * 60 * 1000; // Assume minutes if no unit specified
}
}
async function startStreaming() {
const dataStream = streamData(simulatedData, 5000);
for await (const newData of dataStream) {
console.log("New data received:", new Date(newData.time), newData);
// Update all timeframe arrays
for (let timeframe in dataArrays) {
dataArrays[timeframe] = aggregateData([...dataArrays[timeframe], newData], timeframe);
}
notifySubscribers(newData);
}
console.log("Streaming completed");
}
startStreaming();
const subscribers = new Map();
function notifySubscribers(newData) {
for (let [subscriberUID, handler] of subscribers) {
handler.callback(newData);
}
}
const configurationData = {
supported_resolutions: ["5S", "1", "3", "5", "15", "30", "60", "120", "240", "1D", "1W", "1M"],
exchanges: [{
value: 'Kraken',
name: 'Kraken',
desc: 'Kraken bitcoin exchange',
}],
symbols_types: [{
name: 'crypto',
value: 'crypto',
}],
};
const Datafeed = {
onReady: (callback) => {
console.log('[onReady]: Method call');
setTimeout(() => callback(configurationData));
},
searchSymbols: (userInput, exchange, symbolType, onResultReadyCallback) => {
console.log('[searchSymbols]: Method call');
onResultReadyCallback([]);
},
resolveSymbol: (symbolName, onSymbolResolvedCallback, onResolveErrorCallback) => {
console.log('[resolveSymbol]: Method call', symbolName);
const symbolInfo = {
name: symbolName,
description: symbolName,
type: 'crypto',
session: '24x7',
timezone: 'Etc/UTC',
minmov: 1,
pricescale: 100,
has_intraday: true,
has_seconds: true,
intraday_multipliers: ['1', '60'],
seconds_multipliers: ["5"],
supported_resolutions: configurationData.supported_resolutions,
volume_precision: 2,
data_status: 'streaming',
};
onSymbolResolvedCallback(symbolInfo);
},
getBars: (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => {
const { from, to, firstDataRequest } = periodParams;
console.log('[getBars]: Method call', symbolInfo, resolution, new Date(from * 1000), new Date(to * 1000));
const bars = dataArrays[resolution].filter(bar => {
const barTime = bar.time / 1000;
return barTime >= from && barTime < to;
});
console.log(`[getBars]: returned ${bars.length} bar(s)`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
},
subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscriberUID, onResetCacheNeededCallback) => {
console.log('[subscribeBars]: Method call with subscriberUID:', subscriberUID);
subscribers.set(subscriberUID, { symbolInfo, resolution, callback: onRealtimeCallback });
},
unsubscribeBars: (subscriberUID) => {
console.log('[unsubscribeBars]: Method call with subscriberUID:', subscriberUID);
subscribers.delete(subscriberUID);
},
};
window.tvWidget = new TradingView.widget({
symbol: 'Kraken:BTC/USD',
interval: '5S',
fullscreen: true,
container: 'tv_chart_container',
datafeed: Datafeed,
enabled_features: ["dont_show_boolean_study_arguments", "seconds_resolution"],
library_path: '../charting_library_cloned_data/charting_library/',
time_frames: [
{ text: "1m", resolution: "1", description: "1 Minute", title: "1m" },
{ text: "3m", resolution: "3", description: "3 Minutes", title: "3m" },
{ text: "5m", resolution: "5", description: "5 Minutes", title: "5m" },
{ text: "15m", resolution: "15", description: "15 Minutes", title: "15m" },
{ text: "30m", resolution: "30", description: "30 Minutes", title: "30m" },
{ text: "1h", resolution: "60", description: "1 Hour", title: "1h" },
{ text: "2h", resolution: "120", description: "2 Hours", title: "2h" },
{ text: "4h", resolution: "240", description: "4 Hours", title: "4h" },
{ text: "1D", resolution: "1D", description: "1 Day", title: "1D" },
{ text: "1W", resolution: "1W", description: "1 Week", title: "1W" },
{ text: "1M", resolution: "1M", description: "1 Month", title: "1M" },
],
overrides: {
"mainSeriesProperties.candleStyle.upColor": "#26a69a",
"mainSeriesProperties.candleStyle.downColor": "#ef5350",
"mainSeriesProperties.candleStyle.drawWick": true,
"mainSeriesProperties.candleStyle.drawBorder": true,
"mainSeriesProperties.candleStyle.borderColor": "#378658",
"mainSeriesProperties.candleStyle.borderUpColor": "#26a69a",
"mainSeriesProperties.candleStyle.borderDownColor": "#ef5350",
"mainSeriesProperties.candleStyle.wickUpColor": "#26a69a",
"mainSeriesProperties.candleStyle.wickDownColor": "#ef5350",
},
// Removed the 'time' option to let TradingView auto-adjust
});
</script>
</body>
</html>
Aggregation for All Timeframes: The startStreaming function now uses the aggregateData function to populate all the timeframe arrays in dataArrays whenever a new data point is received.getBars Returns Aggregated Data: The getBars function now returns the appropriate aggregated data from the dataArrays object based on the requested resolution and time range.Timeframes Added to Widget Configuration: The time_frames option in the TradingView widget configuration now includes all the supported timeframes, allowing users to switch between them.Removed time Option: The time option has been removed from the widget configuration, allowing TradingView to automatically adjust the initial time based on the first data point it receives.
Data Streaming and Aggregation: The startStreaming function simulates a stream of new data. For each new data point, it updates all the timeframe arrays in dataArrays using the aggregateData function, which aggregates the data based on the timeframe.Chart Initialization: When the TradingView chart is initialized, it requests historical data from getBars.getBars Provides Data: The getBars function returns the aggregated data for the requested timeframe and time range from the dataArrays object.Realtime Updates: As new data arrives through the onRealtimeCallback, TradingView updates the chart using the replay function internally. The dataArrays are also updated in real-time, ensuring that when you switch between timeframes, you see the correct aggregated data.
getBars: (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => {
const { from, to, firstDataRequest } = periodParams;
console.log('[getBars]: Method call', symbolInfo, resolution, new Date(from * 1000), new Date(to * 1000));
if (resolution === '5S' && firstDataRequest) {
// If it's the first request for 5S, return the entire array
const bars = dataArrays['5S'];
console.log(`[getBars]: returned entire 5S array (${bars.length} bar(s))`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
} else {
// For other requests (including subsequent 5S requests), filter as usual
const bars = dataArrays[resolution].filter(bar => {
const barTime = bar.time / 1000;
return barTime >= from && barTime < to;
});
console.log(`[getBars]: returned ${bars.length} bar(s)`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
}
},
We now check if resolution === '5S' and firstDataRequest === true. If both conditions are met, it means it's the initial request for the 5S timeframe, and we return the entire dataArrays['5S'] array. Otherwise, we filter the array based on from and to as before.
<!DOCTYPE HTML>
<html>
<head>
<title>TradingView Advanced Charts with Replay API</title>
</head>
<body style="margin:0px;">
<div id="tv_chart_container"></div>
<script type="text/javascript" src="charting_library_cloned_data/charting_library/charting_library.js"></script>
<script type="module">
const simulatedData = [
{ time: 1725594300000, open: 25093.7, high: 25120.4, low: 25088.4, close: 25113.95, volume: 0 },
{ time: 1725594305000, open: 25120.65, high: 25131.65, low: 25118.15, close: 25126.1, volume: 0 },
{ time: 1725594310000, open: 25121.85, high: 25121.85, low: 25114.2, close: 25117.85, volume: 0 },
{ time: 1725594315000, open: 25115.6, high: 25118.2, low: 25111.25, close: 25114.4, volume: 0 },
{ time: 1725594320000, open: 25117.6, high: 25117.6, low: 25108.75, close: 25108.75, volume: 0 },
{ time: 1725594325000, open: 25108.5, high: 25122.85, low: 25108.25, close: 25122.85, volume: 0 },
{ time: 1725594330000, open: 25121.4, high: 25125.3, low: 25118.0, close: 25118.0, volume: 0 },
{ time: 1725594335000, open: 25118.2, high: 25118.65, low: 25116.7, close: 25118.35, volume: 0 }
];
const dataArrays = {
'5S': [],
'1': [],
'3': [],
'5': [],
'15': [],
'30': [],
'60': [],
'120': [],
'240': [],
'1D': [],
'1W': [],
'1M': []
};
function streamData(data, interval = 5000) {
// ... (same as before)
}
function aggregateData(data, timeframe) {
// ... (same as before)
}
function getTimeframeMs(timeframe) {
// ... (same as before)
}
async function startStreaming() {
// ... (same as before)
}
startStreaming();
const subscribers = new Map();
function notifySubscribers(newData) {
// ... (same as before)
}
const configurationData = {
// ... (same as before)
};
const Datafeed = {
onReady: (callback) => {
// ... (same as before)
},
searchSymbols: (userInput, exchange, symbolType, onResultReadyCallback) => {
// ... (same as before)
},
resolveSymbol: (symbolName, onSymbolResolvedCallback, onResolveErrorCallback) => {
// ... (same as before)
},
getBars: (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => {
const { from, to, firstDataRequest } = periodParams;
console.log('[getBars]: Method call', symbolInfo, resolution, new Date(from * 1000), new Date(to * 1000));
if (resolution === '5S' && firstDataRequest) {
// If it's the first request for 5S, return the entire array
const bars = dataArrays['5S'];
console.log(`[getBars]: returned entire 5S array (${bars.length} bar(s))`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
} else {
// For other requests (including subsequent 5S requests), filter as usual
const bars = dataArrays[resolution].filter(bar => {
const barTime = bar.time / 1000;
return barTime >= from && barTime < to;
});
console.log(`[getBars]: returned ${bars.length} bar(s)`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
}
},
subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscriberUID, onResetCacheNeededCallback) => {
// ... (same as before)
},
unsubscribeBars: (subscriberUID) => {
// ... (same as before)
},
};
window.tvWidget = new TradingView.widget({
// ... (rest of your widget configuration)
});
</script>
</body>
</html>
getBars function: We added a condition: if (resolution === '5S' && firstDataRequest). If this condition is true, it means it's the initial request for the 5S timeframe, and we return the entire dataArrays['5S'] array without filtering. Otherwise (for other timeframes or subsequent 5S requests), we filter the array based on the from and to parameters as before.
<!DOCTYPE HTML>
<html>
<head>
<title>TradingView Advanced Charts with Replay API</title>
</head>
<body style="margin:0px;">
<div id="tv_chart_container"></div>
<script type="text/javascript" src="charting_library_cloned_data/charting_library/charting_library.js"></script>
<script type="module">
const simulatedData = [
{ time: 1725594300000, open: 25093.7, high: 25120.4, low: 25088.4, close: 25113.95, volume: 0 },
{ time: 1725594305000, open: 25120.65, high: 25131.65, low: 25118.15, close: 25126.1, volume: 0 },
{ time: 1725594310000, open: 25121.85, high: 25121.85, low: 25114.2, close: 25117.85, volume: 0 },
{ time: 1725594315000, open: 25115.6, high: 25118.2, low: 25111.25, close: 25114.4, volume: 0 },
{ time: 1725594320000, open: 25117.6, high: 25117.6, low: 25108.75, close: 25108.75, volume: 0 },
{ time: 1725594325000, open: 25108.5, high: 25122.85, low: 25108.25, close: 25122.85, volume: 0 },
{ time: 1725594330000, open: 25121.4, high: 25125.3, low: 25118.0, close: 25118.0, volume: 0 },
{ time: 1725594335000, open: 25118.2, high: 25118.65, low: 25116.7, close: 25118.35, volume: 0 }
];
const dataArrays = {
'5S': [],
'1': [],
'3': [],
'5': [],
'15': [],
'30': [],
'60': [],
'120': [],
'240': [],
'1D': [],
'1W': [],
'1M': []
};
function streamData(data, interval = 5000) {
let index = 0;
return {
next() {
return new Promise((resolve) => {
if (index < data.length) {
setTimeout(() => {
resolve({ value: data[index++], done: false });
}, interval);
} else {
resolve({ done: true });
}
});
},
[Symbol.asyncIterator]() {
return this;
}
};
}
function aggregateData(data, timeframe) {
const aggregated = [];
let currentBucket = null;
const timeframeMs = getTimeframeMs(timeframe);
for (let row of data) {
const bucketTime = Math.floor(row.time / timeframeMs) * timeframeMs;
if (!currentBucket || currentBucket.time !== bucketTime) {
if (currentBucket) {
aggregated.push(currentBucket);
}
currentBucket = {
time: bucketTime,
open: row.open,
high: row.high,
low: row.low,
close: row.close,
volume: row.volume
};
} else {
currentBucket.high = Math.max(currentBucket.high, row.high);
currentBucket.low = Math.min(currentBucket.low, row.low);
currentBucket.close = row.close;
currentBucket.volume += row.volume;
}
}
if (currentBucket) {
aggregated.push(currentBucket);
}
return aggregated;
}
function getTimeframeMs(timeframe) {
const unit = timeframe.slice(-1);
const value = parseInt(timeframe);
switch (unit) {
case 'S': return value * 1000;
case 'D': return value * 24 * 60 * 60 * 1000;
case 'W': return value * 7 * 24 * 60 * 60 * 1000;
case 'M': return value * 30 * 24 * 60 * 60 * 1000;
default: return value * 60 * 1000; // Assume minutes if no unit specified
}
}
async function startStreaming() {
const dataStream = streamData(simulatedData, 5000);
for await (const newData of dataStream) {
console.log("New data received:", new Date(newData.time), newData);
// Update all timeframe arrays
for (let timeframe in dataArrays) {
dataArrays[timeframe] = aggregateData([...dataArrays[timeframe], newData], timeframe);
}
notifySubscribers(newData);
}
console.log("Streaming completed");
}
startStreaming();
const subscribers = new Map();
function notifySubscribers(newData) {
for (let [subscriberUID, handler] of subscribers) {
handler.callback(newData);
}
}
const configurationData = {
supported_resolutions: ["5S", "1", "3", "5", "15", "30", "60", "120", "240", "1D", "1W", "1M"],
exchanges: [{
value: 'Kraken',
name: 'Kraken',
desc: 'Kraken bitcoin exchange',
}],
symbols_types: [{
name: 'crypto',
value: 'crypto',
}],
};
const Datafeed = {
onReady: (callback) => {
console.log('[onReady]: Method call');
setTimeout(() => callback(configurationData));
},
searchSymbols: (userInput, exchange, symbolType, onResultReadyCallback) => {
console.log('[searchSymbols]: Method call');
onResultReadyCallback([]);
},
resolveSymbol: (symbolName, onSymbolResolvedCallback, onResolveErrorCallback) => {
console.log('[resolveSymbol]: Method call', symbolName);
const symbolInfo = {
name: symbolName,
description: symbolName,
type: 'crypto',
session: '24x7',
timezone: 'Etc/UTC',
minmov: 1,
pricescale: 100,
has_intraday: true,
has_seconds: true,
intraday_multipliers: ['1', '60'],
seconds_multipliers: ["5"],
supported_resolutions: configurationData.supported_resolutions,
volume_precision: 2,
data_status: 'streaming',
};
onSymbolResolvedCallback(symbolInfo);
},
getBars: (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => {
const { from, to, firstDataRequest } = periodParams;
console.log('[getBars]: Method call', symbolInfo, resolution, new Date(from * 1000), new Date(to * 1000));
if (resolution === '5S' && firstDataRequest) {
// If it's the first request for 5S, return the entire array
const bars = dataArrays['5S'];
console.log(`[getBars]: returned entire 5S array (${bars.length} bar(s))`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
} else {
// For other requests (including subsequent 5S requests), filter as usual
const bars = dataArrays[resolution].filter(bar => {
const barTime = bar.time / 1000;
return barTime >= from && barTime < to;
});
console.log(`[getBars]: returned ${bars.length} bar(s)`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
}
},
subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscriberUID, onResetCacheNeededCallback) => {
console.log('[subscribeBars]: Method call with subscriberUID:', subscriberUID);
subscribers.set(subscriberUID, { symbolInfo, resolution, callback: onRealtimeCallback });
},
unsubscribeBars: (subscriberUID) => {
console.log('[unsubscribeBars]: Method call with subscriberUID:', subscriberUID);
subscribers.delete(subscriberUID);
},
};
window.tvWidget = new TradingView.widget({
symbol: 'Kraken:BTC/USD',
interval: '5S',
fullscreen: true,
container: 'tv_chart_container',
datafeed: Datafeed,
enabled_features: ["dont_show_boolean_study_arguments", "seconds_resolution"],
library_path: '../charting_library_cloned_data/charting_library/',
time_frames: [
{ text: "1m", resolution: "1", description: "1 Minute", title: "1m" },
{ text: "3m", resolution: "3", description: "3 Minutes", title: "3m" },
{ text: "5m", resolution: "5", description: "5 Minutes", title: "5m" },
{ text: "15m", resolution: "15", description: "15 Minutes", title: "15m" },
{ text: "30m", resolution: "30", description: "30 Minutes", title: "30m" },
{ text: "1h", resolution: "60", description: "1 Hour", title: "1h" },
{ text: "2h", resolution: "120", description: "2 Hours", title: "2h" },
{ text: "4h", resolution: "240", description: "4 Hours", title: "4h" },
{ text: "1D", resolution: "1D", description: "1 Day", title: "1D" },
{ text: "1W", resolution: "1W", description: "1 Week", title: "1W" },
{ text: "1M", resolution: "1M", description: "1 Month", title: "1M" },
],
overrides: {
"mainSeriesProperties.candleStyle.upColor": "#26a69a",
"mainSeriesProperties.candleStyle.downColor": "#ef5350",
"mainSeriesProperties.candleStyle.drawWick": true,
"mainSeriesProperties.candleStyle.drawBorder": true,
"mainSeriesProperties.candleStyle.borderColor": "#378658",
"mainSeriesProperties.candleStyle.borderUpColor": "#26a69a",
"mainSeriesProperties.candleStyle.borderDownColor": "#ef5350",
"mainSeriesProperties.candleStyle.wickUpColor": "#26a69a",
"mainSeriesProperties.candleStyle.wickDownColor": "#ef5350",
},
// Removed the 'time' option to let TradingView auto-adjust
});
</script>
</body>
</html>
getBars: (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => {
// ... (rest of the function)
if (resolution === '5S' && firstDataRequest) {
// If it's the first request for 5S, return the entire array
const bars = dataArrays['5S'];
// ... (rest of the if block)
} else {
// For other requests (including subsequent 5S requests), filter as usual
// ... (rest of the else block)
}
},
Resampling Function: Create a function that takes 5-second data as input and resamples it to a specific timeframe (e.g., 1 minute, 5 minutes, etc.). This function will essentially aggregate the 5-second data into the desired timeframe, calculating the open, high, low, close, and volume for each new bar.function resampleData(data, timeframe) { const resampledData = []; let currentBar = null; const timeframeMs = getTimeframeMs(timeframe); // You already have this function for (const row of data) { const barTime = Math.floor(row.time / timeframeMs) * timeframeMs; if (!currentBar || currentBar.time !== barTime) { if (currentBar) { resampledData.push(currentBar); } currentBar = { time: barTime, open: row.open, high: row.high, low: row.low, close: row.close, volume: row.volume }; } else { currentBar.high = Math.max(currentBar.high, row.high); currentBar.low = Math.min(currentBar.low, row.low); currentBar.close = row.close; currentBar.volume += row.volume; } } if (currentBar) { resampledData.push(currentBar); } return resampledData; }Integrate Resampling in startStreaming: Modify your startStreaming function to resample the new data before appending it to the higher timeframe arrays.async function startStreaming() { const dataStream = streamData(simulatedData, 5000); for await (const newData of dataStream) { console.log("New data received:", new Date(newData.time), newData); // Update 5S array directly dataArrays['5S'].push(newData); // Resample and update other timeframe arrays for (let timeframe in dataArrays) { if (timeframe !== '5S') { const resampledData = resampleData([newData], timeframe); // Resample dataArrays[timeframe] = aggregateData([...dataArrays[timeframe], ...resampledData], timeframe); } } notifySubscribers(newData); } console.log("Streaming completed"); }
The resampleData function takes the new 5-second data and the desired timeframe as input and returns an array of resampled data for that timeframe. In startStreaming, we now call resampleData for each timeframe other than 5S. We then use the aggregateData function (which you already have) to properly aggregate the resampled data with the existing data in the timeframe array.
<!DOCTYPE HTML>
<html>
<head>
<title>TradingView Advanced Charts with Replay API</title>
</head>
<body style="margin:0px;">
<div id="tv_chart_container"></div>
<script type="text/javascript" src="charting_library_cloned_data/charting_library/charting_library.js"></script>
<script type="module">
const simulatedData = [
{ time: 1725594300000, open: 25093.7, high: 25120.4, low: 25088.4, close: 25113.95, volume: 0 },
{ time: 1725594305000, open: 25120.65, high: 25131.65, low: 25118.15, close: 25126.1, volume: 0 },
{ time: 1725594310000, open: 25121.85, high: 25121.85, low: 25114.2, close: 25117.85, volume: 0 },
{ time: 1725594315000, open: 25115.6, high: 25118.2, low: 25111.25, close: 25114.4, volume: 0 },
{ time: 1725594320000, open: 25117.6, high: 25117.6, low: 25108.75, close: 25108.75, volume: 0 },
{ time: 1725594325000, open: 25108.5, high: 25122.85, low: 25108.25, close: 25122.85, volume: 0 },
{ time: 1725594330000, open: 25121.4, high: 25125.3, low: 25118.0, close: 25118.0, volume: 0 },
{ time: 1725594335000, open: 25118.2, high: 25118.65, low: 25116.7, close: 25118.35, volume: 0 }
];
const dataArrays = {
'5S': [],
'1': [],
'3': [],
'5': [],
'15': [],
'30': [],
'60': [],
'120': [],
'240': [],
'1D': [],
'1W': [],
'1M': []
};
function streamData(data, interval = 5000) {
// ... (same as before)
}
function aggregateData(data, timeframe) {
// ... (same as before)
}
function getTimeframeMs(timeframe) {
// ... (same as before)
}
function resampleData(data, timeframe) {
const resampledData = [];
let currentBar = null;
const timeframeMs = getTimeframeMs(timeframe);
for (const row of data) {
const barTime = Math.floor(row.time / timeframeMs) * timeframeMs;
if (!currentBar || currentBar.time !== barTime) {
if (currentBar) {
resampledData.push(currentBar);
}
currentBar = {
time: barTime,
open: row.open,
high: row.high,
low: row.low,
close: row.close,
volume: row.volume
};
} else {
currentBar.high = Math.max(currentBar.high, row.high);
currentBar.low = Math.min(currentBar.low, row.low);
currentBar.close = row.close;
currentBar.volume += row.volume;
}
}
if (currentBar) {
resampledData.push(currentBar);
}
return resampledData;
}
async function startStreaming() {
const dataStream = streamData(simulatedData, 5000);
for await (const newData of dataStream) {
console.log("New data received:", new Date(newData.time), newData);
// Update 5S array directly
dataArrays['5S'].push(newData);
// Resample and update other timeframe arrays
for (let timeframe in dataArrays) {
if (timeframe !== '5S') {
const resampledData = resampleData([newData], timeframe); // Resample
dataArrays[timeframe] = aggregateData([...dataArrays[timeframe], ...resampledData], timeframe);
}
}
notifySubscribers(newData);
}
console.log("Streaming completed");
}
startStreaming();
const subscribers = new Map();
function notifySubscribers(newData) {
// ... (same as before)
}
const configurationData = {
// ... (same as before)
};
const Datafeed = {
onReady: (callback) => {
// ... (same as before)
},
searchSymbols: (userInput, exchange, symbolType, onResultReadyCallback) => {
// ... (same as before)
},
resolveSymbol: (symbolName, onSymbolResolvedCallback, onResolveErrorCallback) => {
// ... (same as before)
},
getBars: (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => {
const { from, to, firstDataRequest } = periodParams;
console.log('[getBars]: Method call', symbolInfo, resolution, new Date(from * 1000), new Date(to * 1000));
if (resolution === '5S' && firstDataRequest) {
// If it's the first request for 5S, return the entire array
const bars = dataArrays['5S'];
console.log(`[getBars]: returned entire 5S array (${bars.length} bar(s))`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
} else {
// For other requests (including subsequent 5S requests), filter as usual
const bars = dataArrays[resolution].filter(bar => {
const barTime = bar.time / 1000;
return barTime >= from && barTime < to;
});
console.log(`[getBars]: returned ${bars.length} bar(s)`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
}
},
subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscriberUID, onResetCacheNeededCallback) => {
// ... (same as before)
},
unsubscribeBars: (subscriberUID) => {
// ... (same as before)
},
};
window.tvWidget = new TradingView.widget({
// ... (rest of your widget configuration)
});
</script>
</body>
</html>
resampleData Function: Added the resampleData function, which takes 5-second data and a timeframe as input and returns resampled data for that timeframe.startStreaming Modification: Modified startStreaming to:Update the dataArrays['5S'] array directly with the new 5-second data. Resample the new data using resampleData for all other timeframes. Aggregate the resampled data with existing data in the corresponding timeframe array using aggregateData.
<!DOCTYPE HTML>
<html>
<head>
<title>TradingView Advanced Charts with Replay API</title>
</head>
<body style="margin:0px;">
<div id="tv_chart_container"></div>
<script type="text/javascript" src="charting_library_cloned_data/charting_library/charting_library.js"></script>
<script type="module">
const simulatedData = [
{ time: 1725594300000, open: 25093.7, high: 25120.4, low: 25088.4, close: 25113.95, volume: 0 },
{ time: 1725594305000, open: 25120.65, high: 25131.65, low: 25118.15, close: 25126.1, volume: 0 },
{ time: 1725594310000, open: 25121.85, high: 25121.85, low: 25114.2, close: 25117.85, volume: 0 },
{ time: 1725594315000, open: 25115.6, high: 25118.2, low: 25111.25, close: 25114.4, volume: 0 },
{ time: 1725594320000, open: 25117.6, high: 25117.6, low: 25108.75, close: 25108.75, volume: 0 },
{ time: 1725594325000, open: 25108.5, high: 25122.85, low: 25108.25, close: 25122.85, volume: 0 },
{ time: 1725594330000, open: 25121.4, high: 25125.3, low: 25118.0, close: 25118.0, volume: 0 },
{ time: 1725594335000, open: 25118.2, high: 25118.65, low: 25116.7, close: 25118.35, volume: 0 }
];
const dataArrays = {
'5S': [],
'1': [],
'3': [],
'5': [],
'15': [],
'30': [],
'60': [],
'120': [],
'240': [],
'1D': [],
'1W': [],
'1M': []
};
function streamData(data, interval = 5000) {
let index = 0;
return {
next() {
return new Promise((resolve) => {
if (index < data.length) {
setTimeout(() => {
resolve({ value: data[index++], done: false });
}, interval);
} else {
resolve({ done: true });
}
});
},
[Symbol.asyncIterator]() {
return this;
}
};
}
function aggregateData(data, timeframe) {
const aggregated = [];
let currentBucket = null;
const timeframeMs = getTimeframeMs(timeframe);
for (let row of data) {
const bucketTime = Math.floor(row.time / timeframeMs) * timeframeMs;
if (!currentBucket || currentBucket.time !== bucketTime) {
if (currentBucket) {
aggregated.push(currentBucket);
}
currentBucket = {
time: bucketTime,
open: row.open,
high: row.high,
low: row.low,
close: row.close,
volume: row.volume
};
} else {
currentBucket.high = Math.max(currentBucket.high, row.high);
currentBucket.low = Math.min(currentBucket.low, row.low);
currentBucket.close = row.close;
currentBucket.volume += row.volume;
}
}
if (currentBucket) {
aggregated.push(currentBucket);
}
return aggregated;
}
function getTimeframeMs(timeframe) {
const unit = timeframe.slice(-1);
const value = parseInt(timeframe);
switch (unit) {
case 'S': return value * 1000;
case 'D': return value * 24 * 60 * 60 * 1000;
case 'W': return value * 7 * 24 * 60 * 60 * 1000;
case 'M': return value * 30 * 24 * 60 * 60 * 1000;
default: return value * 60 * 1000; // Assume minutes if no unit specified
}
}
function resampleData(data, timeframe) {
const resampledData = [];
let currentBar = null;
const timeframeMs = getTimeframeMs(timeframe);
for (const row of data) {
const barTime = Math.floor(row.time / timeframeMs) * timeframeMs;
if (!currentBar || currentBar.time !== barTime) {
if (currentBar) {
resampledData.push(currentBar);
}
currentBar = {
time: barTime,
open: row.open,
high: row.high,
low: row.low,
close: row.close,
volume: row.volume
};
} else {
currentBar.high = Math.max(currentBar.high, row.high);
currentBar.low = Math.min(currentBar.low, row.low);
currentBar.close = row.close;
currentBar.volume += row.volume;
}
}
if (currentBar) {
resampledData.push(currentBar);
}
return resampledData;
}
async function startStreaming() {
const dataStream = streamData(simulatedData, 5000);
for await (const newData of dataStream) {
console.log("New data received:", new Date(newData.time), newData);
// Update 5S array directly
dataArrays['5S'].push(newData);
// Resample and update other timeframe arrays
for (let timeframe in dataArrays) {
if (timeframe !== '5S') {
const resampledData = resampleData([newData], timeframe); // Resample
dataArrays[timeframe] = aggregateData([...dataArrays[timeframe], ...resampledData], timeframe);
}
}
notifySubscribers(newData);
}
console.log("Streaming completed");
}
startStreaming();
const subscribers = new Map();
function notifySubscribers(newData) {
for (let [subscriberUID, handler] of subscribers) {
handler.callback(newData);
}
}
const configurationData = {
supported_resolutions: ["5S", "1", "3", "5", "15", "30", "60", "120", "240", "1D", "1W", "1M"],
exchanges: [{
value: 'Kraken',
name: 'Kraken',
desc: 'Kraken bitcoin exchange',
}],
symbols_types: [{
name: 'crypto',
value: 'crypto',
}],
};
const Datafeed = {
onReady: (callback) => {
console.log('[onReady]: Method call');
setTimeout(() => callback(configurationData));
},
searchSymbols: (userInput, exchange, symbolType, onResultReadyCallback) => {
console.log('[searchSymbols]: Method call');
onResultReadyCallback([]);
},
resolveSymbol: (symbolName, onSymbolResolvedCallback, onResolveErrorCallback) => {
console.log('[resolveSymbol]: Method call', symbolName);
const symbolInfo = {
name: symbolName,
description: symbolName,
type: 'crypto',
session: '24x7',
timezone: 'Etc/UTC',
minmov: 1,
pricescale: 100,
has_intraday: true,
has_seconds: true,
intraday_multipliers: ['1', '60'],
seconds_multipliers: ["5"],
supported_resolutions: configurationData.supported_resolutions,
volume_precision: 2,
data_status: 'streaming',
};
onSymbolResolvedCallback(symbolInfo);
},
getBars: (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => {
const { from, to, firstDataRequest } = periodParams;
console.log('[getBars]: Method call', symbolInfo, resolution, new Date(from * 1000), new Date(to * 1000));
if (resolution === '5S' && firstDataRequest) {
// If it's the first request for 5S, return the entire array
const bars = dataArrays['5S'];
console.log(`[getBars]: returned entire 5S array (${bars.length} bar(s))`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
} else {
// For other requests (including subsequent 5S requests), filter as usual
const bars = dataArrays[resolution].filter(bar => {
const barTime = bar.time / 1000;
return barTime >= from && barTime < to;
});
console.log(`[getBars]: returned ${bars.length} bar(s)`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
}
},
subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscriberUID, onResetCacheNeededCallback) => {
console.log('[subscribeBars]: Method call with subscriberUID:', subscriberUID);
subscribers.set(subscriberUID, { symbolInfo, resolution, callback: onRealtimeCallback });
},
unsubscribeBars: (subscriberUID) => {
console.log('[unsubscribeBars]: Method call with subscriberUID:', subscriberUID);
subscribers.delete(subscriberUID);
},
};
window.tvWidget = new TradingView.widget({
symbol: 'Kraken:BTC/USD',
interval: '5S',
fullscreen: true,
container: 'tv_chart_container',
datafeed: Datafeed,
enabled_features: ["dont_show_boolean_study_arguments", "seconds_resolution"],
library_path: '../charting_library_cloned_data/charting_library/',
time_frames: [
{ text: "1m", resolution: "1", description: "1 Minute", title: "1m" },
{ text: "3m", resolution: "3", description: "3 Minutes", title: "3m" },
{ text: "5m", resolution: "5", description: "5 Minutes", title: "5m" },
{ text: "15m", resolution: "15", description: "15 Minutes", title: "15m" },
{ text: "30m", resolution: "30", description: "30 Minutes", title: "30m" },
{ text: "1h", resolution: "60", description: "1 Hour", title: "1h" },
{ text: "2h", resolution: "120", description: "2 Hours", title: "2h" },
{ text: "4h", resolution: "240", description: "4 Hours", title: "4h" },
{ text: "1D", resolution: "1D", description: "1 Day", title: "1D" },
{ text: "1W", resolution: "1W", description: "1 Week", title: "1W" },
{ text: "1M", resolution: "1M", description: "1 Month", title: "1M" },
],
overrides: {
"mainSeriesProperties.candleStyle.upColor": "#26a69a",
"mainSeriesProperties.candleStyle.downColor": "#ef5350",
"mainSeriesProperties.candleStyle.drawWick": true,
"mainSeriesProperties.candleStyle.drawBorder": true,
"mainSeriesProperties.candleStyle.borderColor": "#378658",
"mainSeriesProperties.candleStyle.borderUpColor": "#26a69a",
"mainSeriesProperties.candleStyle.borderDownColor": "#ef5350",
"mainSeriesProperties.candleStyle.wickUpColor": "#26a69a",
"mainSeriesProperties.candleStyle.wickDownColor": "#ef5350",
},
// Removed the 'time' option to let TradingView auto-adjust
});
</script>
</body>
</html>
async function startStreaming() {
const dataStream = streamData(simulatedData, 5000);
for await (const newData of dataStream) {
console.log("New data received:", new Date(newData.time), newData);
// Update 5S array directly
dataArrays['5S'].push(newData);
// Resample and update other timeframe arrays
for (let timeframe in dataArrays) {
if (timeframe !== '5S') {
const resampledData = resampleData([newData], timeframe); // Resample
// Append the resampled data to the timeframe array
dataArrays[timeframe].push(...resampledData);
}
}
notifySubscribers(newData);
}
console.log("Streaming completed");
}
We now simply append the resampledData to the dataArrays[timeframe] array using the spread operator (...). We removed the extra call to aggregateData within the loop, as it was unnecessary and potentially causing issues.
<!DOCTYPE HTML>
<html>
<head>
<title>TradingView Advanced Charts with Replay API</title>
</head>
<body style="margin:0px;">
<div id="tv_chart_container"></div>
<script type="text/javascript" src="charting_library_cloned_data/charting_library/charting_library.js"></script>
<script type="module">
const simulatedData = [
{ time: 1725594300000, open: 25093.7, high: 25120.4, low: 25088.4, close: 25113.95, volume: 0 },
{ time: 1725594305000, open: 25120.65, high: 25131.65, low: 25118.15, close: 25126.1, volume: 0 },
{ time: 1725594310000, open: 25121.85, high: 25121.85, low: 25114.2, close: 25117.85, volume: 0 },
{ time: 1725594315000, open: 25115.6, high: 25118.2, low: 25111.25, close: 25114.4, volume: 0 },
{ time: 1725594320000, open: 25117.6, high: 25117.6, low: 25108.75, close: 25108.75, volume: 0 },
{ time: 1725594325000, open: 25108.5, high: 25122.85, low: 25108.25, close: 25122.85, volume: 0 },
{ time: 1725594330000, open: 25121.4, high: 25125.3, low: 25118.0, close: 25118.0, volume: 0 },
{ time: 1725594335000, open: 25118.2, high: 25118.65, low: 25116.7, close: 25118.35, volume: 0 }
];
const dataArrays = {
'5S': [],
'1': [],
'3': [],
'5': [],
'15': [],
'30': [],
'60': [],
'120': [],
'240': [],
'1D': [],
'1W': [],
'1M': []
};
function streamData(data, interval = 5000) {
// ... (same as before)
}
function aggregateData(data, timeframe) {
// ... (same as before)
}
function getTimeframeMs(timeframe) {
// ... (same as before)
}
function resampleData(data, timeframe) {
// ... (same as before)
}
async function startStreaming() {
const dataStream = streamData(simulatedData, 5000);
for await (const newData of dataStream) {
console.log("New data received:", new Date(newData.time), newData);
// Update 5S array directly
dataArrays['5S'].push(newData);
// Resample and update other timeframe arrays
for (let timeframe in dataArrays) {
if (timeframe !== '5S') {
const resampledData = resampleData([newData], timeframe); // Resample
// Append the resampled data to the timeframe array (corrected)
dataArrays[timeframe].push(...resampledData);
}
}
notifySubscribers(newData);
}
console.log("Streaming completed");
}
startStreaming();
const subscribers = new Map();
function notifySubscribers(newData) {
// ... (same as before)
}
const configurationData = {
// ... (same as before)
};
const Datafeed = {
onReady: (callback) => {
// ... (same as before)
},
searchSymbols: (userInput, exchange, symbolType, onResultReadyCallback) => {
// ... (same as before)
},
resolveSymbol: (symbolName, onSymbolResolvedCallback, onResolveErrorCallback) => {
// ... (same as before)
},
getBars: (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => {
const { from, to, firstDataRequest } = periodParams;
console.log('[getBars]: Method call', symbolInfo, resolution, new Date(from * 1000), new Date(to * 1000));
if (resolution === '5S' && firstDataRequest) {
// If it's the first request for 5S, return the entire array
const bars = dataArrays['5S'];
console.log(`[getBars]: returned entire 5S array (${bars.length} bar(s))`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
} else {
// For other requests (including subsequent 5S requests), filter as usual
const bars = dataArrays[resolution].filter(bar => {
const barTime = bar.time / 1000;
return barTime >= from && barTime < to;
});
console.log(`[getBars]: returned ${bars.length} bar(s)`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
}
},
subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscriberUID, onResetCacheNeededCallback) => {
// ... (same as before)
},
unsubscribeBars: (subscriberUID) => {
// ... (same as before)
},
};
window.tvWidget = new TradingView.widget({
// ... (rest of your widget configuration)
});
</script>
</body>
</html>
startStreaming Function (Corrected): The line where we append the resampled data to the timeframe array has been corrected to: dataArrays[timeframe].push(...resampledData);
<!DOCTYPE HTML>
<html>
<head>
<title>TradingView Advanced Charts with Replay API</title>
</head>
<body style="margin:0px;">
<div id="tv_chart_container"></div>
<script type="text/javascript" src="charting_library_cloned_data/charting_library/charting_library.js"></script>
<script type="module">
const simulatedData = [
{ time: 1725594300000, open: 25093.7, high: 25120.4, low: 25088.4, close: 25113.95, volume: 0 },
{ time: 1725594305000, open: 25120.65, high: 25131.65, low: 25118.15, close: 25126.1, volume: 0 },
{ time: 1725594310000, open: 25121.85, high: 25121.85, low: 25114.2, close: 25117.85, volume: 0 },
{ time: 1725594315000, open: 25115.6, high: 25118.2, low: 25111.25, close: 25114.4, volume: 0 },
{ time: 1725594320000, open: 25117.6, high: 25117.6, low: 25108.75, close: 25108.75, volume: 0 },
{ time: 1725594325000, open: 25108.5, high: 25122.85, low: 25108.25, close: 25122.85, volume: 0 },
{ time: 1725594330000, open: 25121.4, high: 25125.3, low: 25118.0, close: 25118.0, volume: 0 },
{ time: 1725594335000, open: 25118.2, high: 25118.65, low: 25116.7, close: 25118.35, volume: 0 }
];
const dataArrays = {
'5S': [],
'1': [],
'3': [],
'5': [],
'15': [],
'30': [],
'60': [],
'120': [],
'240': [],
'1D': [],
'1W': [],
'1M': []
};
function streamData(data, interval = 5000) {
let index = 0;
return {
next() {
return new Promise((resolve) => {
if (index < data.length) {
setTimeout(() => {
resolve({ value: data[index++], done: false });
}, interval);
} else {
resolve({ done: true });
}
});
},
[Symbol.asyncIterator]() {
return this;
}
};
}
function aggregateData(data, timeframe) {
const aggregated = [];
let currentBucket = null;
const timeframeMs = getTimeframeMs(timeframe);
for (let row of data) {
const bucketTime = Math.floor(row.time / timeframeMs) * timeframeMs;
if (!currentBucket || currentBucket.time !== bucketTime) {
if (currentBucket) {
aggregated.push(currentBucket);
}
currentBucket = {
time: bucketTime,
open: row.open,
high: row.high,
low: row.low,
close: row.close,
volume: row.volume
};
} else {
currentBucket.high = Math.max(currentBucket.high, row.high);
currentBucket.low = Math.min(currentBucket.low, row.low);
currentBucket.close = row.close;
currentBucket.volume += row.volume;
}
}
if (currentBucket) {
aggregated.push(currentBucket);
}
return aggregated;
}
function getTimeframeMs(timeframe) {
const unit = timeframe.slice(-1);
const value = parseInt(timeframe);
switch (unit) {
case 'S': return value * 1000;
case 'D': return value * 24 * 60 * 60 * 1000;
case 'W': return value * 7 * 24 * 60 * 60 * 1000;
case 'M': return value * 30 * 24 * 60 * 60 * 1000;
default: return value * 60 * 1000; // Assume minutes if no unit specified
}
}
function resampleData(data, timeframe) {
const resampledData = [];
let currentBar = null;
const timeframeMs = getTimeframeMs(timeframe);
for (const row of data) {
const barTime = Math.floor(row.time / timeframeMs) * timeframeMs;
if (!currentBar || currentBar.time !== barTime) {
if (currentBar) {
resampledData.push(currentBar);
}
currentBar = {
time: barTime,
open: row.open,
high: row.high,
low: row.low,
close: row.close,
volume: row.volume
};
} else {
currentBar.high = Math.max(currentBar.high, row.high);
currentBar.low = Math.min(currentBar.low, row.low);
currentBar.close = row.close;
currentBar.volume += row.volume;
}
}
if (currentBar) {
resampledData.push(currentBar);
}
return resampledData;
}
async function startStreaming() {
const dataStream = streamData(simulatedData, 5000);
for await (const newData of dataStream) {
console.log("New data received:", new Date(newData.time), newData);
// Update 5S array directly
dataArrays['5S'].push(newData);
// Resample and update other timeframe arrays
for (let timeframe in dataArrays) {
if (timeframe !== '5S') {
const resampledData = resampleData([newData], timeframe); // Resample
// Append the resampled data to the timeframe array (corrected)
dataArrays[timeframe].push(...resampledData);
}
}
notifySubscribers(newData);
}
console.log("Streaming completed");
}
startStreaming();
const subscribers = new Map();
function notifySubscribers(newData) {
for (let [subscriberUID, handler] of subscribers) {
handler.callback(newData);
}
}
const configurationData = {
supported_resolutions: ["5S", "1", "3", "5", "15", "30", "60", "120", "240", "1D", "1W", "1M"],
exchanges: [{
value: 'Kraken',
name: 'Kraken',
desc: 'Kraken bitcoin exchange',
}],
symbols_types: [{
name: 'crypto',
value: 'crypto',
}],
};
const Datafeed = {
onReady: (callback) => {
console.log('[onReady]: Method call');
setTimeout(() => callback(configurationData));
},
searchSymbols: (userInput, exchange, symbolType, onResultReadyCallback) => {
console.log('[searchSymbols]: Method call');
onResultReadyCallback([]);
},
resolveSymbol: (symbolName, onSymbolResolvedCallback, onResolveErrorCallback) => {
console.log('[resolveSymbol]: Method call', symbolName);
const symbolInfo = {
name: symbolName,
description: symbolName,
type: 'crypto',
session: '24x7',
timezone: 'Etc/UTC',
minmov: 1,
pricescale: 100,
has_intraday: true,
has_seconds: true,
intraday_multipliers: ['1', '60'],
seconds_multipliers: ["5"],
supported_resolutions: configurationData.supported_resolutions,
volume_precision: 2,
data_status: 'streaming',
};
onSymbolResolvedCallback(symbolInfo);
},
getBars: (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => {
const { from, to, firstDataRequest } = periodParams;
console.log('[getBars]: Method call', symbolInfo, resolution, new Date(from * 1000), new Date(to * 1000));
if (resolution === '5S' && firstDataRequest) {
// If it's the first request for 5S, return the entire array
const bars = dataArrays['5S'];
console.log(`[getBars]: returned entire 5S array (${bars.length} bar(s))`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
} else {
// For other requests (including subsequent 5S requests), filter as usual
const bars = dataArrays[resolution].filter(bar => {
const barTime = bar.time / 1000;
return barTime >= from && barTime < to;
});
console.log(`[getBars]: returned ${bars.length} bar(s)`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
}
},
subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscriberUID, onResetCacheNeededCallback) => {
console.log('[subscribeBars]: Method call with subscriberUID:', subscriberUID);
subscribers.set(subscriberUID, { symbolInfo, resolution, callback: onRealtimeCallback });
},
unsubscribeBars: (subscriberUID) => {
console.log('[unsubscribeBars]: Method call with subscriberUID:', subscriberUID);
subscribers.delete(subscriberUID);
},
};
window.tvWidget = new TradingView.widget({
symbol: 'Kraken:BTC/USD',
interval: '5S',
fullscreen: true,
container: 'tv_chart_container',
datafeed: Datafeed,
enabled_features: ["dont_show_boolean_study_arguments", "seconds_resolution"],
library_path: '../charting_library_cloned_data/charting_library/',
time_frames: [
{ text: "1m", resolution: "1", description: "1 Minute", title: "1m" },
{ text: "3m", resolution: "3", description: "3 Minutes", title: "3m" },
{ text: "5m", resolution: "5", description: "5 Minutes", title: "5m" },
{ text: "15m", resolution: "15", description: "15 Minutes", title: "15m" },
{ text: "30m", resolution: "30", description: "30 Minutes", title: "30m" },
{ text: "1h", resolution: "60", description: "1 Hour", title: "1h" },
{ text: "2h", resolution: "120", description: "2 Hours", title: "2h" },
{ text: "4h", resolution: "240", description: "4 Hours", title: "4h" },
{ text: "1D", resolution: "1D", description: "1 Day", title: "1D" },
{ text: "1W", resolution: "1W", description: "1 Week", title: "1W" },
{ text: "1M", resolution: "1M", description: "1 Month", title: "1M" },
],
overrides: {
"mainSeriesProperties.candleStyle.upColor": "#26a69a",
"mainSeriesProperties.candleStyle.downColor": "#ef5350",
"mainSeriesProperties.candleStyle.drawWick": true,
"mainSeriesProperties.candleStyle.drawBorder": true,
"mainSeriesProperties.candleStyle.borderColor": "#378658",
"mainSeriesProperties.candleStyle.borderUpColor": "#26a69a",
"mainSeriesProperties.candleStyle.borderDownColor": "#ef5350",
"mainSeriesProperties.candleStyle.wickUpColor": "#26a69a",
"mainSeriesProperties.candleStyle.wickDownColor": "#ef5350",
},
// Removed the 'time' option to let TradingView auto-adjust
});
</script>
</body>
</html>
function notifySubscribers(newData) {
for (let [subscriberUID, handler] of subscribers) {
const { resolution } = handler;
if (resolution === '5S') {
// Send raw 5S data for 5S timeframe
handler.callback(newData);
} else {
// Resample data to the subscriber's resolution
const resampledData = resampleData([newData], resolution);
if (resampledData.length > 0) { // Send only if resampling produced data
handler.callback(resampledData[0]); // Send the resampled bar
}
}
}
}
We now check the subscriber's resolution (handler.resolution). If it's '5S', we send the raw newData as before. If it's a different resolution, we resample the newData using the resampleData function to the subscriber's resolution. We then send the resampled bar (if resampling produced data) to the subscriber's callback.
<!DOCTYPE HTML>
<html>
<head>
<title>TradingView Advanced Charts with Replay API</title>
</head>
<body style="margin:0px;">
<div id="tv_chart_container"></div>
<script type="text/javascript" src="charting_library_cloned_data/charting_library/charting_library.js"></script>
<script type="module">
// ... (rest of the code - simulatedData, dataArrays, streamData, aggregateData, getTimeframeMs, resampleData, startStreaming)
const subscribers = new Map();
function notifySubscribers(newData) {
for (let [subscriberUID, handler] of subscribers) {
const { resolution } = handler;
if (resolution === '5S') {
// Send raw 5S data for 5S timeframe
handler.callback(newData);
} else {
// Resample data to the subscriber's resolution
const resampledData = resampleData([newData], resolution);
if (resampledData.length > 0) { // Send only if resampling produced data
handler.callback(resampledData[0]); // Send the resampled bar
}
}
}
}
// ... (rest of the code - configurationData, Datafeed, window.tvWidget)
</script>
</body>
</html>
<!DOCTYPE HTML>
<html>
<head>
<title>TradingView Advanced Charts with Replay API</title>
</head>
<body style="margin:0px;">
<div id="tv_chart_container"></div>
<script type="text/javascript" src="charting_library_cloned_data/charting_library/charting_library.js"></script>
<script type="module">
const simulatedData = [
{ time: 1725594300000, open: 25093.7, high: 25120.4, low: 25088.4, close: 25113.95, volume: 0 },
{ time: 1725594305000, open: 25120.65, high: 25131.65, low: 25118.15, close: 25126.1, volume: 0 },
{ time: 1725594310000, open: 25121.85, high: 25121.85, low: 25114.2, close: 25117.85, volume: 0 },
{ time: 1725594315000, open: 25115.6, high: 25118.2, low: 25111.25, close: 25114.4, volume: 0 },
{ time: 1725594320000, open: 25117.6, high: 25117.6, low: 25108.75, close: 25108.75, volume: 0 },
{ time: 1725594325000, open: 25108.5, high: 25122.85, low: 25108.25, close: 25122.85, volume: 0 },
{ time: 1725594330000, open: 25121.4, high: 25125.3, low: 25118.0, close: 25118.0, volume: 0 },
{ time: 1725594335000, open: 25118.2, high: 25118.65, low: 25116.7, close: 25118.35, volume: 0 }
];
const dataArrays = {
'5S': [],
'1': [],
'3': [],
'5': [],
'15': [],
'30': [],
'60': [],
'120': [],
'240': [],
'1D': [],
'1W': [],
'1M': []
};
function streamData(data, interval = 5000) {
let index = 0;
return {
next() {
return new Promise((resolve) => {
if (index < data.length) {
setTimeout(() => {
resolve({ value: data[index++], done: false });
}, interval);
} else {
resolve({ done: true });
}
});
},
[Symbol.asyncIterator]() {
return this;
}
};
}
function aggregateData(data, timeframe) {
const aggregated = [];
let currentBucket = null;
const timeframeMs = getTimeframeMs(timeframe);
for (let row of data) {
const bucketTime = Math.floor(row.time / timeframeMs) * timeframeMs;
if (!currentBucket || currentBucket.time !== bucketTime) {
if (currentBucket) {
aggregated.push(currentBucket);
}
currentBucket = {
time: bucketTime,
open: row.open,
high: row.high,
low: row.low,
close: row.close,
volume: row.volume
};
} else {
currentBucket.high = Math.max(currentBucket.high, row.high);
currentBucket.low = Math.min(currentBucket.low, row.low);
currentBucket.close = row.close;
currentBucket.volume += row.volume;
}
}
if (currentBucket) {
aggregated.push(currentBucket);
}
return aggregated;
}
function getTimeframeMs(timeframe) {
const unit = timeframe.slice(-1);
const value = parseInt(timeframe);
switch (unit) {
case 'S': return value * 1000;
case 'D': return value * 24 * 60 * 60 * 1000;
case 'W': return value * 7 * 24 * 60 * 60 * 1000;
case 'M': return value * 30 * 24 * 60 * 60 * 1000;
default: return value * 60 * 1000; // Assume minutes if no unit specified
}
}
function resampleData(data, timeframe) {
const resampledData = [];
let currentBar = null;
const timeframeMs = getTimeframeMs(timeframe);
for (const row of data) {
const barTime = Math.floor(row.time / timeframeMs) * timeframeMs;
if (!currentBar || currentBar.time !== barTime) {
if (currentBar) {
resampledData.push(currentBar);
}
currentBar = {
time: barTime,
open: row.open,
high: row.high,
low: row.low,
close: row.close,
volume: row.volume
};
} else {
currentBar.high = Math.max(currentBar.high, row.high);
currentBar.low = Math.min(currentBar.low, row.low);
currentBar.close = row.close;
currentBar.volume += row.volume;
}
}
if (currentBar) {
resampledData.push(currentBar);
}
return resampledData;
}
async function startStreaming() {
const dataStream = streamData(simulatedData, 5000);
for await (const newData of dataStream) {
console.log("New data received:", new Date(newData.time), newData);
// Update 5S array directly
dataArrays['5S'].push(newData);
// Resample and update other timeframe arrays
for (let timeframe in dataArrays) {
if (timeframe !== '5S') {
const resampledData = resampleData([newData], timeframe); // Resample
// Append the resampled data to the timeframe array (corrected)
dataArrays[timeframe].push(...resampledData);
}
}
notifySubscribers(newData);
}
console.log("Streaming completed");
}
startStreaming();
const subscribers = new Map();
function notifySubscribers(newData) {
for (let [subscriberUID, handler] of subscribers) {
const { resolution } = handler;
if (resolution === '5S') {
// Send raw 5S data for 5S timeframe
handler.callback(newData);
} else {
// Resample data to the subscriber's resolution
const resampledData = resampleData([newData], resolution);
if (resampledData.length > 0) { // Send only if resampling produced data
handler.callback(resampledData[0]); // Send the resampled bar
}
}
}
}
const configurationData = {
supported_resolutions: ["5S", "1", "3", "5", "15", "30", "60", "120", "240", "1D", "1W", "1M"],
exchanges: [{
value: 'Kraken',
name: 'Kraken',
desc: 'Kraken bitcoin exchange',
}],
symbols_types: [{
name: 'crypto',
value: 'crypto',
}],
};
const Datafeed = {
onReady: (callback) => {
console.log('[onReady]: Method call');
setTimeout(() => callback(configurationData));
},
searchSymbols: (userInput, exchange, symbolType, onResultReadyCallback) => {
console.log('[searchSymbols]: Method call');
onResultReadyCallback([]);
},
resolveSymbol: (symbolName, onSymbolResolvedCallback, onResolveErrorCallback) => {
console.log('[resolveSymbol]: Method call', symbolName);
const symbolInfo = {
name: symbolName,
description: symbolName,
type: 'crypto',
session: '24x7',
timezone: 'Etc/UTC',
minmov: 1,
pricescale: 100,
has_intraday: true,
has_seconds: true,
intraday_multipliers: ['1', '60'],
seconds_multipliers: ["5"],
supported_resolutions: configurationData.supported_resolutions,
volume_precision: 2,
data_status: 'streaming',
};
onSymbolResolvedCallback(symbolInfo);
},
getBars: (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => {
const { from, to, firstDataRequest } = periodParams;
console.log('[getBars]: Method call', symbolInfo, resolution, new Date(from * 1000), new Date(to * 1000));
if (resolution === '5S' && firstDataRequest) {
// If it's the first request for 5S, return the entire array
const bars = dataArrays['5S'];
console.log(`[getBars]: returned entire 5S array (${bars.length} bar(s))`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
} else {
// For other requests (including subsequent 5S requests), filter as usual
const bars = dataArrays[resolution].filter(bar => {
const barTime = bar.time / 1000;
return barTime >= from && barTime < to;
});
console.log(`[getBars]: returned ${bars.length} bar(s)`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
}
},
subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscriberUID, onResetCacheNeededCallback) => {
console.log('[subscribeBars]: Method call with subscriberUID:', subscriberUID);
subscribers.set(subscriberUID, { symbolInfo, resolution, callback: onRealtimeCallback });
},
unsubscribeBars: (subscriberUID) => {
console.log('[unsubscribeBars]: Method call with subscriberUID:', subscriberUID);
subscribers.delete(subscriberUID);
},
};
window.tvWidget = new TradingView.widget({
symbol: 'Kraken:BTC/USD',
interval: '5S',
fullscreen: true,
container: 'tv_chart_container',
datafeed: Datafeed,
enabled_features: ["dont_show_boolean_study_arguments", "seconds_resolution"],
library_path: '../charting_library_cloned_data/charting_library/',
time_frames: [
{ text: "1m", resolution: "1", description: "1 Minute", title: "1m" },
{ text: "3m", resolution: "3", description: "3 Minutes", title: "3m" },
{ text: "5m", resolution: "5", description: "5 Minutes", title: "5m" },
{ text: "15m", resolution: "15", description: "15 Minutes", title: "15m" },
{ text: "30m", resolution: "30", description: "30 Minutes", title: "30m" },
{ text: "1h", resolution: "60", description: "1 Hour", title: "1h" },
{ text: "2h", resolution: "120", description: "2 Hours", title: "2h" },
{ text: "4h", resolution: "240", description: "4 Hours", title: "4h" },
{ text: "1D", resolution: "1D", description: "1 Day", title: "1D" },
{ text: "1W", resolution: "1W", description: "1 Week", title: "1W" },
{ text: "1M", resolution: "1M", description: "1 Month", title: "1M" },
],
overrides: {
"mainSeriesProperties.candleStyle.upColor": "#26a69a",
"mainSeriesProperties.candleStyle.downColor": "#ef5350",
"mainSeriesProperties.candleStyle.drawWick": true,
"mainSeriesProperties.candleStyle.drawBorder": true,
"mainSeriesProperties.candleStyle.borderColor": "#378658",
"mainSeriesProperties.candleStyle.borderUpColor": "#26a69a",
"mainSeriesProperties.candleStyle.borderDownColor": "#ef5350",
"mainSeriesProperties.candleStyle.wickUpColor": "#26a69a",
"mainSeriesProperties.candleStyle.wickDownColor": "#ef5350",
},
// Removed the 'time' option to let TradingView auto-adjust
});
</script>
</body>
</html>
The dataArrays['5S'] array is updated directly. The resampleData function is called for every timeframe other than '5S' to resample the new data into the corresponding resolution (1 minute, 3 minutes, etc.).The resampled data is then appended to the respective timeframe array (dataArrays['1'], dataArrays['3'], etc.).
Data readiness: When the userdoes switch to a different timeframe, the data is already aggregated and ready to be displayed, making the transition seamless.Efficient resource usage: You avoid unnecessary resampling operations if the user never actually views certain timeframes.
Memory usage: If you have a large amount of data and many supported timeframes, the dataArrays object could become quite large in memory. You might need to implement strategies to manage this if it becomes a concern (e.g., limiting the amount of historical data stored or using a more efficient data structure).
Initial 5-second data: When the chart loads, TradingView requests a certain amount of historical 5-second data (controlled by the visible range and the library's internal logic). This data is used to populate the initial view.Realtime updates: As new 5-second data comes in, it's displayed in real-time, and the higher timeframe arrays are also updated in the background.Switching to a higher timeframe: When you switch to, for example, the 1-minute timeframe, TradingView again requests historical data, but this time for the 1-minute resolution.Limited history request: TradingView likely requests only a limited amount of 1-minute historical data, starting from a point relatively close to the current time. It's possible that this request doesn't go back far enough to retrieve all the 1-minute data that has been aggregated from the beginning of the 5-second stream.
You can modify your getBars function to detect when it's the first request for a higher timeframe (similar to how you handle the initial 5S request). In this case, instead of filtering based on from and to, return the entire corresponding timeframe array (dataArrays['1'], dataArrays['3'], etc.). This will ensure that TradingView receives all the aggregated data from the beginning for that timeframe, even if its initial request doesn't go back that far.
getBars: (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => {
// ... (rest of the function)
if (resolution !== '5S' && firstDataRequest) {
// If it's the first request for a non-5S timeframe, return the entire array
const bars = dataArrays[resolution];
console.log(`[getBars]: returned entire ${resolution} array (${bars.length} bar(s))`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
} else {
// ... (rest of your getBars logic for filtering)
}
},
If you want to avoid sending potentially large amounts of data upfront, you can implement a "Load More" button or functionality within your TradingView chart. When the user clicks "Load More," you can make a separate request to your datafeed to retrieve older data for the current timeframe and append it to the chart.
TradingView's internal behavior: It's important to note that TradingView's internal data loading and caching mechanisms can be complex and might vary depending on factors like the library version, chart settings, and user interactions. Experimentation and careful observation might be needed to find the most reliable solution for your specific setup.
<!DOCTYPE HTML>
<html>
<head>
<title>TradingView Advanced Charts with Replay API</title>
</head>
<body style="margin:0px;">
<div id="tv_chart_container"></div>
<script type="text/javascript" src="charting_library_cloned_data/charting_library/charting_library.js"></script>
<script type="module">
const simulatedData = [
{ time: 1725594300000, open: 25093.7, high: 25120.4, low: 25088.4, close: 25113.95, volume: 0 },
{ time: 1725594305000, open: 25120.65, high: 25131.65, low: 25118.15, close: 25126.1, volume: 0 },
{ time: 1725594310000, open: 25121.85, high: 25121.85, low: 25114.2, close: 25117.85, volume: 0 },
{ time: 1725594315000, open: 25115.6, high: 25118.2, low: 25111.25, close: 25114.4, volume: 0 },
{ time: 1725594320000, open: 25117.6, high: 25117.6, low: 25108.75, close: 25108.75, volume: 0 },
{ time: 1725594325000, open: 25108.5, high: 25122.85, low: 25108.25, close: 25122.85, volume: 0 },
{ time: 1725594330000, open: 25121.4, high: 25125.3, low: 25118.0, close: 25118.0, volume: 0 },
{ time: 1725594335000, open: 25118.2, high: 25118.65, low: 25116.7, close: 25118.35, volume: 0 }
];
const dataArrays = {
'5S': [],
'1': [],
'3': [],
'5': [],
'15': [],
'30': [],
'60': [],
'120': [],
'240': [],
'1D': [],
'1W': [],
'1M': []
};
function streamData(data, interval = 5000) {
// ... (same as before)
}
function aggregateData(data, timeframe) {
// ... (same as before)
}
function getTimeframeMs(timeframe) {
// ... (same as before)
}
function resampleData(data, timeframe) {
// ... (same as before)
}
async function startStreaming() {
// ... (same as before)
}
startStreaming();
const subscribers = new Map();
function notifySubscribers(newData) {
// ... (modified as before to send resampled data)
}
const configurationData = {
// ... (same as before)
};
const Datafeed = {
onReady: (callback) => {
// ... (same as before)
},
searchSymbols: (userInput, exchange, symbolType, onResultReadyCallback) => {
// ... (same as before)
},
resolveSymbol: (symbolName, onSymbolResolvedCallback, onResolveErrorCallback) => {
// ... (same as before)
},
getBars: (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => {
const { from, to, firstDataRequest } = periodParams;
console.log('[getBars]: Method call', symbolInfo, resolution, new Date(from * 1000), new Date(to * 1000));
if ((resolution === '5S' || resolution !== '5S') && firstDataRequest) {
// Return the entire array for the initial request of any timeframe
const bars = dataArrays[resolution];
console.log(`[getBars]: returned entire ${resolution} array (${bars.length} bar(s))`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
} else {
// For subsequent requests, filter as usual
const bars = dataArrays[resolution].filter(bar => {
const barTime = bar.time / 1000;
return barTime >= from && barTime < to;
});
console.log(`[getBars]: returned ${bars.length} bar(s)`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
}
},
subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscriberUID, onResetCacheNeededCallback) => {
// ... (same as before)
},
unsubscribeBars: (subscriberUID) => {
// ... (same as before)
},
};
window.tvWidget = new TradingView.widget({
// ... (rest of your widget configuration)
});
</script>
</body>
</html>
getBars function (modified): The condition in the if statement is changed to: if ((resolution === '5S' || resolution !== '5S') && firstDataRequest) {This ensures that for the initial request of any timeframe (including '5S' and all others), the entire corresponding dataArrays array is returned.
<!DOCTYPE HTML>
<html>
<head>
<title>TradingView Advanced Charts with Replay API</title>
</head>
<body style="margin:0px;">
<div id="tv_chart_container"></div>
<script type="text/javascript" src="charting_library_cloned_data/charting_library/charting_library.js"></script>
<script type="module">
const simulatedData = [
{ time: 1725594300000, open: 25093.7, high: 25120.4, low: 25088.4, close: 25113.95, volume: 0 },
{ time: 1725594305000, open: 25120.65, high: 25131.65, low: 25118.15, close: 25126.1, volume: 0 },
{ time: 1725594310000, open: 25121.85, high: 25121.85, low: 25114.2, close: 25117.85, volume: 0 },
{ time: 1725594315000, open: 25115.6, high: 25118.2, low: 25111.25, close: 25114.4, volume: 0 },
{ time: 1725594320000, open: 25117.6, high: 25117.6, low: 25108.75, close: 25108.75, volume: 0 },
{ time: 1725594325000, open: 25108.5, high: 25122.85, low: 25108.25, close: 25122.85, volume: 0 },
{ time: 1725594330000, open: 25121.4, high: 25125.3, low: 25118.0, close: 25118.0, volume: 0 },
{ time: 1725594335000, open: 25118.2, high: 25118.65, low: 25116.7, close: 25118.35, volume: 0 }
];
const dataArrays = {
'5S': [],
'1': [],
'3': [],
'5': [],
'15': [],
'30': [],
'60': [],
'120': [],
'240': [],
'1D': [],
'1W': [],
'1M': []
};
function streamData(data, interval = 5000) {
let index = 0;
return {
next() {
return new Promise((resolve) => {
if (index < data.length) {
setTimeout(() => {
resolve({ value: data[index++], done: false });
}, interval);
} else {
resolve({ done: true });
}
});
},
[Symbol.asyncIterator]() {
return this;
}
};
}
function aggregateData(data, timeframe) {
const aggregated = [];
let currentBucket = null;
const timeframeMs = getTimeframeMs(timeframe);
for (let row of data) {
const bucketTime = Math.floor(row.time / timeframeMs) * timeframeMs;
if (!currentBucket || currentBucket.time !== bucketTime) {
if (currentBucket) {
aggregated.push(currentBucket);
}
currentBucket = {
time: bucketTime,
open: row.open,
high: row.high,
low: row.low,
close: row.close,
volume: row.volume
};
} else {
currentBucket.high = Math.max(currentBucket.high, row.high);
currentBucket.low = Math.min(currentBucket.low, row.low);
currentBucket.close = row.close;
currentBucket.volume += row.volume;
}
}
if (currentBucket) {
aggregated.push(currentBucket);
}
return aggregated;
}
function getTimeframeMs(timeframe) {
const unit = timeframe.slice(-1);
const value = parseInt(timeframe);
switch (unit) {
case 'S': return value * 1000;
case 'D': return value * 24 * 60 * 60 * 1000;
case 'W': return value * 7 * 24 * 60 * 60 * 1000;
case 'M': return value * 30 * 24 * 60 * 60 * 1000;
default: return value * 60 * 1000; // Assume minutes if no unit specified
}
}
function resampleData(data, timeframe) {
const resampledData = [];
let currentBar = null;
const timeframeMs = getTimeframeMs(timeframe);
for (const row of data) {
const barTime = Math.floor(row.time / timeframeMs) * timeframeMs;
if (!currentBar || currentBar.time !== barTime) {
if (currentBar) {
resampledData.push(currentBar);
}
currentBar = {
time: barTime,
open: row.open,
high: row.high,
low: row.low,
close: row.close,
volume: row.volume
};
} else {
currentBar.high = Math.max(currentBar.high, row.high);
currentBar.low = Math.min(currentBar.low, row.low);
currentBar.close = row.close;
currentBar.volume += row.volume;
}
}
if (currentBar) {
resampledData.push(currentBar);
}
return resampledData;
}
async function startStreaming() {
const dataStream = streamData(simulatedData, 5000);
for await (const newData of dataStream) {
console.log("New data received:", new Date(newData.time), newData);
// Update 5S array directly
dataArrays['5S'].push(newData);
// Resample and update other timeframe arrays
for (let timeframe in dataArrays) {
if (timeframe !== '5S') {
const resampledData = resampleData([newData], timeframe);
dataArrays[timeframe].push(...resampledData);
}
}
notifySubscribers(newData);
}
console.log("Streaming completed");
}
startStreaming();
const subscribers = new Map();
function notifySubscribers(newData) {
for (let [subscriberUID, handler] of subscribers) {
const { resolution } = handler;
if (resolution === '5S') {
handler.callback(newData);
} else {
const resampledData = resampleData([newData], resolution);
if (resampledData.length > 0) {
handler.callback(resampledData[0]);
}
}
}
}
const configurationData = {
supported_resolutions: ["5S", "1", "3", "5", "15", "30", "60", "120", "240", "1D", "1W", "1M"],
exchanges: [{
value: 'Kraken',
name: 'Kraken',
desc: 'Kraken bitcoin exchange',
}],
symbols_types: [{
name: 'crypto',
value: 'crypto',
}],
};
const Datafeed = {
onReady: (callback) => {
console.log('[onReady]: Method call');
setTimeout(() => callback(configurationData));
},
searchSymbols: (userInput, exchange, symbolType, onResultReadyCallback) => {
console.log('[searchSymbols]: Method call');
onResultReadyCallback([]);
},
resolveSymbol: (symbolName, onSymbolResolvedCallback, onResolveErrorCallback) => {
console.log('[resolveSymbol]: Method call', symbolName);
const symbolInfo = {
name: symbolName,
description: symbolName,
type: 'crypto',
session: '24x7',
timezone: 'Etc/UTC',
minmov: 1,
pricescale: 100,
has_intraday: true,
has_seconds: true,
intraday_multipliers: ['1', '60'],
seconds_multipliers: ["5"],
supported_resolutions: configurationData.supported_resolutions,
volume_precision: 2,
data_status: 'streaming',
};
onSymbolResolvedCallback(symbolInfo);
},
getBars: (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => {
const { from, to, firstDataRequest } = periodParams;
console.log('[getBars]: Method call', symbolInfo, resolution, new Date(from * 1000), new Date(to * 1000));
if (firstDataRequest) {
const bars = dataArrays[resolution];
console.log(`[getBars]: returned entire ${resolution} array (${bars.length} bar(s))`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
} else {
const bars = dataArrays[resolution].filter(bar => {
const barTime = bar.time / 1000;
return barTime >= from && barTime < to;
});
console.log(`[getBars]: returned ${bars.length} bar(s)`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
}
},
subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscriberUID, onResetCacheNeededCallback) => {
console.log('[subscribeBars]: Method call with subscriberUID:', subscriberUID);
subscribers.set(subscriberUID, { symbolInfo, resolution, callback: onRealtimeCallback });
},
unsubscribeBars: (subscriberUID) => {
console.log('[unsubscribeBars]: Method call with subscriberUID:', subscriberUID);
subscribers.delete(subscriberUID);
},
};
window.tvWidget = new TradingView.widget({
symbol: 'Kraken:BTC/USD',
interval: '5S',
fullscreen: true,
container: 'tv_chart_container',
datafeed: Datafeed,
enabled_features: ["dont_show_boolean_study_arguments", "seconds_resolution"],
library_path: '../charting_library_cloned_data/charting_library/',
time_frames: [
{ text: "1m", resolution: "1", description: "1 Minute", title: "1m" },
{ text: "3m", resolution: "3", description: "3 Minutes", title: "3m" },
{ text: "5m", resolution: "5", description: "5 Minutes", title: "5m" },
{ text: "15m", resolution: "15", description: "15 Minutes", title: "15m" },
{ text: "30m", resolution: "30", description: "30 Minutes", title: "30m" },
{ text: "1h", resolution: "60", description: "1 Hour", title: "1h" },
{ text: "2h", resolution: "120", description: "2 Hours", title: "2h" },
{ text: "4h", resolution: "240", description: "4 Hours", title: "4h" },
{ text: "1D", resolution: "1D", description: "1 Day", title: "1D" },
{ text: "1W", resolution: "1W", description: "1 Week", title: "1W" },
{ text: "1M", resolution: "1M", description: "1 Month", title: "1M" },
],
overrides: {
"mainSeriesProperties.candleStyle.upColor": "#26a69a",
"mainSeriesProperties.candleStyle.downColor": "#ef5350",
"mainSeriesProperties.candleStyle.drawWick": true,
"mainSeriesProperties.candleStyle.drawBorder": true,
"mainSeriesProperties.candleStyle.borderColor": "#378658",
"mainSeriesProperties.candleStyle.borderUpColor": "#26a69a",
"mainSeriesProperties.candleStyle.borderDownColor": "#ef5350",
"mainSeriesProperties.candleStyle.wickUpColor": "#26a69a",
"mainSeriesProperties.candleStyle.wickDownColor": "#ef5350",
},
});
</script>
</body>
</html>
getBars function: The condition for returning the entire array is simplified to if (firstDataRequest) { ... }, which means this will now happen for the initial data request of any resolution, ensuring that TradingView receives the full data from the beginning.
function resampleData(data, timeframe) {
const resampledData = [];
let currentBar = null;
const timeframeMs = getTimeframeMs(timeframe);
for (const row of data) {
const barTime = Math.floor(row.time / timeframeMs) * timeframeMs;
if (!currentBar || currentBar.time !== barTime) {
// New bar timeframe
if (currentBar) {
resampledData.push(currentBar);
}
currentBar = {
time: barTime,
open: row.open,
high: row.high,
low: row.low,
close: row.close,
volume: row.volume
};
} else {
// Data point falls within the current bar's timeframe
currentBar.high = Math.max(currentBar.high, row.high);
currentBar.low = Math.min(currentBar.low, row.low);
currentBar.close = row.close; // Update close to the latest value
currentBar.volume += row.volume;
}
}
if (currentBar) {
resampledData.push(currentBar);
}
return resampledData;
}
Handling data within the current bar's timeframe: The else block now correctly updates the high, low, close, and volume of the currentBar when a new data point falls within its timeframe. The key change is updating the close price to the latest row.close.
<!DOCTYPE HTML>
<html>
<head>
<title>TradingView Advanced Charts with Replay API</title>
</head>
<body style="margin:0px;">
<div id="tv_chart_container"></div>
<script type="text/javascript" src="charting_library_cloned_data/charting_library/charting_library.js"></script>
<script type="module">
// ... (rest of the code - simulatedData, dataArrays, streamData, aggregateData, getTimeframeMs)
function resampleData(data, timeframe) {
const resampledData = [];
let currentBar = null;
const timeframeMs = getTimeframeMs(timeframe);
for (const row of data) {
const barTime = Math.floor(row.time / timeframeMs) * timeframeMs;
if (!currentBar || currentBar.time !== barTime) {
// New bar timeframe
if (currentBar) {
resampledData.push(currentBar);
}
currentBar = {
time: barTime,
open: row.open,
high: row.high,
low: row.low,
close: row.close,
volume: row.volume
};
} else {
// Data point falls within the current bar's timeframe (corrected)
currentBar.high = Math.max(currentBar.high, row.high);
currentBar.low = Math.min(currentBar.low, row.low);
currentBar.close = row.close; // Update close
currentBar.volume += row.volume;
}
}
if (currentBar) {
resampledData.push(currentBar);
}
return resampledData;
}
// ... (rest of the code - startStreaming, notifySubscribers, configurationData, Datafeed, window.tvWidget)
</script>
</body>
</html>
Single data point resampling: You are currently calling resampleData([newData], timeframe), which only passes a single data point (the latest newData) to the resampling function.Missing context: For larger timeframes, this single data point doesn't provide enough context to determine the accurate open, high, low, and close values. It's essentially treating each 5-second data point as a complete bar for the larger timeframe, which is incorrect.
Accumulation Buffers: Create buffers to accumulate 5-second data points for each timeframe.const accumulationBuffers = { '1': [], '3': [], '5': [], // ... (other timeframes) };Modify startStreaming: Update the startStreaming function to accumulate data in the buffers.async function startStreaming() { const dataStream = streamData(simulatedData, 5000); for await (const newData of dataStream) { // ... (update 5S array) // Accumulate data in buffers for (let timeframe in accumulationBuffers) { accumulationBuffers[timeframe].push(newData); // Check if it's time to resample and update the timeframe array const timeframeMs = getTimeframeMs(timeframe); const lastDataPointTime = accumulationBuffers[timeframe][accumulationBuffers[timeframe].length - 1].time; if (lastDataPointTime - accumulationBuffers[timeframe][0].time >= timeframeMs) { const resampledData = resampleData(accumulationBuffers[timeframe], timeframe); dataArrays[timeframe].push(...resampledData); accumulationBuffers[timeframe] = []; // Clear the buffer } } notifySubscribers(newData); } // ... }
Accumulation: The startStreaming function now accumulates 5-second data points in the accumulationBuffers for each timeframe.Resampling Trigger: It checks if the accumulated data in a buffer spans a full timeframe (e.g., 1 minute, 1 week). If it does, the buffer is resampled, the resampled data is added to the corresponding dataArrays, and the buffer is cleared.
<!DOCTYPE HTML>
<html>
<head>
<title>TradingView Advanced Charts with Replay API</title>
</head>
<body style="margin:0px;">
<div id="tv_chart_container"></div>
<script type="text/javascript" src="charting_library_cloned_data/charting_library/charting_library.js"></script>
<script type="module">
// ... (rest of the code - simulatedData, dataArrays, streamData, aggregateData, getTimeframeMs, resampleData)
const accumulationBuffers = {
'1': [],
'3': [],
'5': [],
// ... (other timeframes)
};
async function startStreaming() {
const dataStream = streamData(simulatedData, 5000);
for await (const newData of dataStream) {
// ... (update 5S array)
// Accumulate data in buffers
for (let timeframe in accumulationBuffers) {
accumulationBuffers[timeframe].push(newData);
// Check if it's time to resample and update the timeframe array
const timeframeMs = getTimeframeMs(timeframe);
const lastDataPointTime = accumulationBuffers[timeframe][accumulationBuffers[timeframe].length - 1].time;
if (lastDataPointTime - accumulationBuffers[timeframe][0].time >= timeframeMs) {
const resampledData = resampleData(accumulationBuffers[timeframe], timeframe);
dataArrays[timeframe].push(...resampledData);
accumulationBuffers[timeframe] = []; // Clear the buffer
}
}
notifySubscribers(newData);
}
// ...
}
// ... (rest of the code - startStreaming, notifySubscribers, configurationData, Datafeed, window.tvWidget)
</script>
</body>
</html>
<!DOCTYPE HTML>
<html>
<head>
<title>TradingView Advanced Charts with Replay API</title>
</head>
<body style="margin:0px;">
<div id="tv_chart_container"></div>
<script type="text/javascript" src="charting_library_cloned_data/charting_library/charting_library.js"></script>
<script type="module">
const simulatedData = [
{ time: 1725594300000, open: 25093.7, high: 25120.4, low: 25088.4, close: 25113.95, volume: 0 },
{ time: 1725594305000, open: 25120.65, high: 25131.65, low: 25118.15, close: 25126.1, volume: 0 },
{ time: 1725594310000, open: 25121.85, high: 25121.85, low: 25114.2, close: 25117.85, volume: 0 },
{ time: 1725594315000, open: 25115.6, high: 25118.2, low: 25111.25, close: 25114.4, volume: 0 },
{ time: 1725594320000, open: 25117.6, high: 25117.6, low: 25108.75, close: 25108.75, volume: 0 },
{ time: 1725594325000, open: 25108.5, high: 25122.85, low: 25108.25, close: 25122.85, volume: 0 },
{ time: 1725594330000, open: 25121.4, high: 25125.3, low: 25118.0, close: 25118.0, volume: 0 },
{ time: 1725594335000, open: 25118.2, high: 25118.65, low: 25116.7, close: 25118.35, volume: 0 }
];
const dataArrays = {
'5S': [],
'1': [],
'3': [],
'5': [],
'15': [],
'30': [],
'60': [],
'120': [],
'240': [],
'1D': [],
'1W': [],
'1M': []
};
// Accumulation buffers for each timeframe (except 5S)
const accumulationBuffers = {
'1': [],
'3': [],
'5': [],
'15': [],
'30': [],
'60': [],
'120': [],
'240': [],
'1D': [],
'1W': [],
'1M': []
};
function streamData(data, interval = 5000) {
// ... (same as before)
}
function aggregateData(data, timeframe) {
// ... (same as before)
}
function getTimeframeMs(timeframe) {
// ... (same as before)
}
function resampleData(data, timeframe) {
// ... (corrected as in the previous response)
}
async function startStreaming() {
const dataStream = streamData(simulatedData, 5000);
for await (const newData of dataStream) {
console.log("New data received:", new Date(newData.time), newData);
// Update 5S array directly
dataArrays['5S'].push(newData);
// Accumulate data in buffers and resample when necessary
for (let timeframe in accumulationBuffers) {
accumulationBuffers[timeframe].push(newData);
const timeframeMs = getTimeframeMs(timeframe);
const lastDataPointTime = accumulationBuffers[timeframe][accumulationBuffers[timeframe].length - 1].time;
if (lastDataPointTime - accumulationBuffers[timeframe][0].time >= timeframeMs) {
const resampledData = resampleData(accumulationBuffers[timeframe], timeframe);
dataArrays[timeframe].push(...resampledData);
accumulationBuffers[timeframe] = []; // Clear the buffer
}
}
notifySubscribers(newData);
}
console.log("Streaming completed");
}
startStreaming();
const subscribers = new Map();
function notifySubscribers(newData) {
for (let [subscriberUID, handler] of subscribers) {
const { resolution } = handler;
if (resolution === '5S') {
handler.callback(newData);
} else {
const resampledData = resampleData([newData], resolution);
if (resampledData.length > 0) {
handler.callback(resampledData[0]);
}
}
}
}
const configurationData = {
// ... (same as before)
};
const Datafeed = {
// ... (same as before)
getBars: (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => {
const { from, to, firstDataRequest } = periodParams;
console.log('[getBars]: Method call', symbolInfo, resolution, new Date(from * 1000), new Date(to * 1000));
if (firstDataRequest) {
const bars = dataArrays[resolution];
console.log(`[getBars]: returned entire ${resolution} array (${bars.length} bar(s))`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
} else {
const bars = dataArrays[resolution].filter(bar => {
const barTime = bar.time / 1000;
return barTime >= from && barTime < to;
});
console.log(`[getBars]: returned ${bars.length} bar(s)`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
}
},
// ... (rest of Datafeed)
};
window.tvWidget = new TradingView.widget({
// ... (rest of your widget configuration)
});
</script>
</body>
</html>
accumulationBuffers: Added an object to hold accumulation buffers for each timeframe other than '5S'.startStreaming: Accumulates 5-second data in the appropriate accumulationBuffers. Resamples the accumulated data when a full timeframe is reached. Clears the buffer after resampling.
resampleData: The function remains corrected as in the previous response to accurately handle data points within the current bar's timeframe.
<!DOCTYPE HTML>
<html>
<head>
<title>TradingView Advanced Charts with Replay API</title>
</head>
<body style="margin:0px;">
<div id="tv_chart_container"></div>
<script type="text/javascript" src="charting_library_cloned_data/charting_library/charting_library.js"></script>
<script type="module">
const simulatedData = [
{ time: 1725594300000, open: 25093.7, high: 25120.4, low: 25088.4, close: 25113.95, volume: 0 },
{ time: 1725594305000, open: 25120.65, high: 25131.65, low: 25118.15, close: 25126.1, volume: 0 },
{ time: 1725594310000, open: 25121.85, high: 25121.85, low: 25114.2, close: 25117.85, volume: 0 },
{ time: 1725594315000, open: 25115.6, high: 25118.2, low: 25111.25, close: 25114.4, volume: 0 },
{ time: 1725594320000, open: 25117.6, high: 25117.6, low: 25108.75, close: 25108.75, volume: 0 },
{ time: 1725594325000, open: 25108.5, high: 25122.85, low: 25108.25, close: 25122.85, volume: 0 },
{ time: 1725594330000, open: 25121.4, high: 25125.3, low: 25118.0, close: 25118.0, volume: 0 },
{ time: 1725594335000, open: 25118.2, high: 25118.65, low: 25116.7, close: 25118.35, volume: 0 }
];
const dataArrays = {
'5S': [],
'1': [],
'3': [],
'5': [],
'15': [],
'30': [],
'60': [],
'120': [],
'240': [],
'1D': [],
'1W': [],
'1M': []
};
// Accumulation buffers for each timeframe (except 5S)
const accumulationBuffers = {
'1': [],
'3': [],
'5': [],
'15': [],
'30': [],
'60': [],
'120': [],
'240': [],
'1D': [],
'1W': [],
'1M': []
};
function streamData(data, interval = 5000) {
let index = 0;
return {
next() {
return new Promise((resolve) => {
if (index < data.length) {
setTimeout(() => {
resolve({ value: data[index++], done: false });
}, interval);
} else {
resolve({ done: true });
}
});
},
[Symbol.asyncIterator]() {
return this;
}
};
}
function aggregateData(data, timeframe) {
const aggregated = [];
let currentBucket = null;
const timeframeMs = getTimeframeMs(timeframe);
for (let row of data) {
const bucketTime = Math.floor(row.time / timeframeMs) * timeframeMs;
if (!currentBucket || currentBucket.time !== bucketTime) {
if (currentBucket) {
aggregated.push(currentBucket);
}
currentBucket = {
time: bucketTime,
open: row.open,
high: row.high,
low: row.low,
close: row.close,
volume: row.volume
};
} else {
currentBucket.high = Math.max(currentBucket.high, row.high);
currentBucket.low = Math.min(currentBucket.low, row.low);
currentBucket.close = row.close;
currentBucket.volume += row.volume;
}
}
if (currentBucket) {
aggregated.push(currentBucket);
}
return aggregated;
}
function getTimeframeMs(timeframe) {
const unit = timeframe.slice(-1);
const value = parseInt(timeframe);
switch (unit) {
case 'S': return value * 1000;
case 'D': return value * 24 * 60 * 60 * 1000;
case 'W': return value * 7 * 24 * 60 * 60 * 1000;
case 'M': return value * 30 * 24 * 60 * 60 * 1000;
default: return value * 60 * 1000; // Assume minutes if no unit specified
}
}
function resampleData(data, timeframe) {
const resampledData = [];
let currentBar = null;
const timeframeMs = getTimeframeMs(timeframe);
for (const row of data) {
const barTime = Math.floor(row.time / timeframeMs) * timeframeMs;
if (!currentBar || currentBar.time !== barTime) {
if (currentBar) {
resampledData.push(currentBar);
}
currentBar = {
time: barTime,
open: row.open,
high: row.high,
low: row.low,
close: row.close,
volume: row.volume
};
} else {
currentBar.high = Math.max(currentBar.high, row.high);
currentBar.low = Math.min(currentBar.low, row.low);
currentBar.close = row.close; // Update close
currentBar.volume += row.volume;
}
}
if (currentBar) {
resampledData.push(currentBar);
}
return resampledData;
}
async function startStreaming() {
const dataStream = streamData(simulatedData, 5000);
for await (const newData of dataStream) {
console.log("New data received:", new Date(newData.time), newData);
// Update 5S array directly
dataArrays['5S'].push(newData);
// Accumulate data in buffers and resample when necessary
for (let timeframe in accumulationBuffers) {
accumulationBuffers[timeframe].push(newData);
const timeframeMs = getTimeframeMs(timeframe);
const lastDataPointTime = accumulationBuffers[timeframe][accumulationBuffers[timeframe].length - 1].time;
if (lastDataPointTime - accumulationBuffers[timeframe][0].time >= timeframeMs) {
const resampledData = resampleData(accumulationBuffers[timeframe], timeframe);
dataArrays[timeframe].push(...resampledData);
accumulationBuffers[timeframe] = []; // Clear the buffer
}
}
notifySubscribers(newData);
}
console.log("Streaming completed");
}
startStreaming();
const subscribers = new Map();
function notifySubscribers(newData) {
for (let [subscriberUID, handler] of subscribers) {
const { resolution } = handler;
if (resolution === '5S') {
handler.callback(newData);
} else {
const resampledData = resampleData([newData], resolution);
if (resampledData.length > 0) {
handler.callback(resampledData[0]);
}
}
}
}
const configurationData = {
supported_resolutions: ["5S", "1", "3", "5", "15", "30", "60", "120", "240", "1D", "1W", "1M"],
exchanges: [{
value: 'Kraken',
name: 'Kraken',
desc: 'Kraken bitcoin exchange',
}],
symbols_types: [{
name: 'crypto',
value: 'crypto',
}],
};
const Datafeed = {
onReady: (callback) => {
console.log('[onReady]: Method call');
setTimeout(() => callback(configurationData));
},
searchSymbols: (userInput, exchange, symbolType, onResultReadyCallback) => {
console.log('[searchSymbols]: Method call');
onResultReadyCallback([]);
},
resolveSymbol: (symbolName, onSymbolResolvedCallback, onResolveErrorCallback) => {
console.log('[resolveSymbol]: Method call', symbolName);
const symbolInfo = {
name: symbolName,
description: symbolName,
type: 'crypto',
session: '24x7',
timezone: 'Etc/UTC',
minmov: 1,
pricescale: 100,
has_intraday: true,
has_seconds: true,
intraday_multipliers: ['1', '60'],
seconds_multipliers: ["5"],
supported_resolutions: configurationData.supported_resolutions,
volume_precision: 2,
data_status: 'streaming',
};
onSymbolResolvedCallback(symbolInfo);
},
getBars: (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => {
const { from, to, firstDataRequest } = periodParams;
console.log('[getBars]: Method call', symbolInfo, resolution, new Date(from * 1000), new Date(to * 1000));
if (firstDataRequest) {
const bars = dataArrays[resolution];
console.log(`[getBars]: returned entire ${resolution} array (${bars.length} bar(s))`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
} else {
const bars = dataArrays[resolution].filter(bar => {
const barTime = bar.time / 1000;
return barTime >= from && barTime < to;
});
console.log(`[getBars]: returned ${bars.length} bar(s)`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
}
},
subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscriberUID, onResetCacheNeededCallback) => {
console.log('[subscribeBars]: Method call with subscriberUID:', subscriberUID);
subscribers.set(subscriberUID, { symbolInfo, resolution, callback: onRealtimeCallback });
},
unsubscribeBars: (subscriberUID) => {
console.log('[unsubscribeBars]: Method call with subscriberUID:', subscriberUID);
subscribers.delete(subscriberUID);
},
};
window.tvWidget = new TradingView.widget({
symbol: 'Kraken:BTC/USD',
interval: '5S',
fullscreen: true,
container: 'tv_chart_container',
datafeed: Datafeed,
enabled_features: ["dont_show_boolean_study_arguments", "seconds_resolution"],
library_path: '../charting_library_cloned_data/charting_library/',
time_frames: [
{ text: "1m", resolution: "1", description: "1 Minute", title: "1m" },
{ text: "3m", resolution: "3", description: "3 Minutes", title: "3m" },
{ text: "5m", resolution: "5", description: "5 Minutes", title: "5m" },
{ text: "15m", resolution: "15", description: "15 Minutes", title: "15m" },
{ text: "30m", resolution: "30", description: "30 Minutes", title: "30m" },
{ text: "1h", resolution: "60", description: "1 Hour", title: "1h" },
{ text: "2h", resolution: "120", description: "2 Hours", title: "2h" },
{ text: "4h", resolution: "240", description: "4 Hours", title: "4h" },
{ text: "1D", resolution: "1D", description: "1 Day", title: "1D" },
{ text: "1W", resolution: "1W", description: "1 Week", title: "1W" },
{ text: "1M", resolution: "1M", description: "1 Month", title: "1M" },
],
overrides: {
"mainSeriesProperties.candleStyle.upColor": "#26a69a",
"mainSeriesProperties.candleStyle.downColor": "#ef5350",
"mainSeriesProperties.candleStyle.drawWick": true,
"mainSeriesProperties.candleStyle.drawBorder": true,
"mainSeriesProperties.candleStyle.borderColor": "#378658",
"mainSeriesProperties.candleStyle.borderUpColor": "#26a69a",
"mainSeriesProperties.candleStyle.borderDownColor": "#ef5350",
"mainSeriesProperties.candleStyle.wickUpColor": "#26a69a",
"mainSeriesProperties.candleStyle.wickDownColor": "#ef5350",
},
});
</script>
</body>
</html>